5 ms·
Modifying string litetals has never worked on any platform I've run code on the past 20 years. They're always in .rodata. I can't imagine doing this by default
by jcalvinowens 2y ago
Modifying string litetals has never worked on any platform I've run code on the past 20 years. They're always in .rodata. I can't imagine doing this by default would be a problem except for really old code.
- zabzonk 2y agoYes, but C (or C++, for that matter) has no concept of .rodata. This is something that needs to be enforced by the compiler, as it is in C++, and why C programmers should probably simply use a C++ compiler, with its much stronger type checking.
- jcalvinowens 2y agoYou missed the point: I'm saying it has been impossible to modify string literals forever, so enforcing const is probably a non-issue except in very old C.
- zabzonk 2y agoIt is completely possible to write C code which does attempt to write to string literals.
- jcalvinowens 2y agoNo, it isn't. It will crash.
- mmastrac 2y agoIt is possible. Some platforms have no concept of rodata. You can mremap a segment. Lots of valid ways to do it.
- jcalvinowens 2y agoWell, at that point you deserve it :)
- moefh 2y agoIt might crash, or it might work as naively expected, or it might do something else. For example, clang started simply omitting writes to data it knows to be read-only (which is allowed because these writes are undefined behavior, so anything goes). See this example[1]: `writable()` will return "*ello", but `readonly()` will just return "hello" and not crash (note its assembly doesn't include a write). [1] https://godbolt.org/z/MboK3hTPx https://godbolt.org/z/MboK3hTPx
- jcalvinowens 2y agoThat happens because the string is static. If you rewrite that so s is an argument to writable(), it will segfault. Although, I am curious if that optimization could happen across compilation units via LTO...
- moefh 2y agoI'm not sure what you mean. In `writable()`, there's no read-only data; `s` is a non-const char array (it has to be static because the function returns a pointer to it). The string literal is only there to tell the compiler how to initialize the array, `s` is not actually the string literal. If you change `writable()` to receive a `const char *` (and then cast it to `char *` to write), then clang will be forced to compile it with a store (even though it sees you storing to a `const char *`) because it doesn't know if the function will be called with a pointer to actual read-only data or just a pointer to writable data that was gratuitously converted to `const`.
- jcalvinowens 2y ago> because it doesn't know if the function will be called with a pointer to actual read-only data or just a pointer to writable data that was gratuitously That's exactly my point yeah, the optimization you described is only possible because you gave the compiler extra knowledge about the argument to that function (because it was static in the same compilation unit). It's artificial, typically that won't be the case.
- acchow 2y agoA good reason to define it as an invalid program then and fail at compile time?
- jcalvinowens 2y agoYes. I am literally arguing for doing that in this entire thread.
- moefh 2y agoThe same is trivially true for C++: https://godbolt.org/z/h5znfchf8 https://godbolt.org/z/h5znfchf8
- jcalvinowens 2y agoI was just editing my comment to add this point :)
- kazinator 2y agoBut that uses a cast. Moreover, an old style cast. GNU c++ has an opt-in warning for those, -Wold-style-cast. You then need const_cast to get around that. Then we can grep the program for that new style cast (unless it token-pasted the const_cast together in the preprocessor haha). In C we can make such a program which contains no devices that defeat the type system, and which otherwise requires no diagnostic.
- the_svd_doctor 2y agoRight but some code will stop compiling, no?
- jcalvinowens 2y agoYes. But such code can be fixed without functional changes. I'm not denying that there are codebases where trying this would result in an Armageddon of refactoring, but I would venture that's the exception rather than the rule. Most C programmers use "const char*" for string literals, and have for a long time.
- deleted 2y ago[deleted]
- hun3 2y agoThe affected platforms lack an OS (e.g., bootloaders) and/or an MMU/MPU (e.g., microprocessors like AVR)
- jcalvinowens 2y agoI don't care about platform specific stuff. I'm talking about C which is actually intended to be portable. Nothing written with portability in mind in the past ~decade is going to be doing this.
- dyhi55 2y agoC is not node.js. C exists for 50 years and is expected to have stable API. In scientific circles it's not unusual to compile c and f77 libraries built in the 70's, 80's. BLAS, gemv, GEMM, SGEMM libraries are from 1979, 1984, 1989. You may have seen these words scroll by when compiling modern 2025 CUDA :)
- jcalvinowens 2y agoI was writing C long before node.js existed :) C has no backwards compatibility guarantee, and it never has. Try compiling K&R C with gcc's defaults, and see what happens. You can build your legacy code with legacy compiler flags. Why do you care about the ability to build under the modern standards?
- hun3 2y agoI think we're going a bit past each other. In AVR or other MPU-less architecture you can literally modify the string literal memory without triggering a crash. Why? Because there is no memory protection ("rodata") at all. And such microprocessors are still in use today, so it's a bit too far fetched to say "really old code." It's UB, sure, but how many embedded programmers actually care? The OP's proposal is trying to change the type system so that this UB becomes much less likely to trigger in practice.
- _kst_ 2y agoThe C standard, since 1989, has said that attempting to modify the array object corresponding to a string literal has undefined behavior. Whether it "works" or not is not the issue. The problem is that it's currently legal to pass a string literal to a function expecting a (non-const) pointer-to-char argument. As long as the function doesn't try to write through the pointer, there's no undefined behavior. (If the function does try to write through the pointer, the behavior is undefined, but no compile-time diagnostic is required.) If a future version of C made string literals const, such a program would become invalid (a constraint violation requiring a diagnostic). Such code was common in pre-ANSI C, before const was introduced to the language. The following is currently valid C. The corresponding C++ code would be invalid. The proposal would make it invalid in C, with the cost of breaking some existing code, and the advantage of catching certain errors at compile time. #include <stdio.h> void print_message(char *message) { puts(message); // *message = '\0'; // would have undefined behavior } int main(void) { print_message("hello"); }
- jcalvinowens 2y ago> Whether it "works" or not is not the issue. Of course it is. It doesn't work on anything modern, and thus it is impossible for portable code which actually runs in the real world and has to work to have relied on it for a long time. Your example is not code any competent C programmer would ever write, IMHO. Every proficient C programmer I've ever worked with used "const char *" for string literals, and called out anybody who didn't in review. Old code already needs special flags to build with modern compilers: I think the benefit of doing this outweighs the cost of editing some makefiles.
- ncruces 2y agoThe most current SQLite amalgamation (3.49.1) is showing ~70 warnings when compiled with -Wwrite-strings. But maybe 70 warnings in 250k LoC is OK for your standards of proficiency.
- jcalvinowens 2y ago
- kazinator 2y agoIf you suddenly make string literals const char, tons of previously correct code will require diagnostics. Code which requires diagnostics is incorrect and has undefined behavior if it is translated and executed anyway. C++ went through this over 20 years ago. I can't remember if it was already in c++03 or whether it was a post '03 draft feature.