36

What advantage(s) of string literals being read-only justify(-ies/-ied) the:

  1. Yet another way to shoot yourself in the foot

    char *foo = "bar";
    foo[0] = 'd'; /* SEGFAULT */
    
  2. Inability to elegantly initialize a read-write array of words in one line:

    char *foo[] = { "bar", "baz", "running out of traditional placeholder names" };
    foo[1][2] = 'n'; /* SEGFAULT */ 
    
  3. Complicating the language itself.

    char *foo = "bar";
    char var[] = "baz";
    some_func(foo); /* VERY DANGEROUS! */
    some_func(var); /* LESS DANGEROUS! */
    

Saving memory? I've read somewhere (couldn't find the source now) that long time ago, when RAM was scarce, compilers tried to optimize memory usage by merging similar strings.

For example, "more" and "regex" would become "moregex". Is this still true today, in the era of digital blu-ray quality movies? I understand that embedded systems still operate in environment of restricted resources, but still, the amount of memory available has increased dramatically.

Compatibility issues? I assume that a legacy program that would try to access read-only memory would either crash or continue with undiscovered bug. Thus no legacy program should try to access string literal and therefor allowing to write to string literal would not harm valid, non-hackish, portable legacy programs.

Are there any other reasons? Is my reasoning incorrect? Would it be reasonable to consider a change to read-write string literals in new C standards or at least add an option to compiler? Was this considered before or are my "problems" too minor and insignificant to bother anyone?

9
  • 15
    I assume you've looked at how string literals look in compiled code?
    – user40980
    Commented Aug 27, 2015 at 19:29
  • 2
    Look at the assembly that the link I provided contains. Its right there.
    – user40980
    Commented Aug 27, 2015 at 19:37
  • 9
    Your "moregex" example wouldn't work because of null termination.
    – dan04
    Commented Aug 27, 2015 at 19:39
  • 5
    You don't want to write over constants because that will change their value. The next time you want to use the same constant it would be different. The compiler/runtime has to source the constants from somewhere, and wherever that is you shouldn't be allowed to modify.
    – Erik Eidt
    Commented Aug 27, 2015 at 20:23
  • 2
    'So string literals are stored in program memory, not RAM, and buffer overflow would result in the corruption of program itself?' The program image is in RAM too. To be precise, the string literals are stored in the same segment of RAM used to store the program image. And yes, overwriting the string could corrupt the program. Back in the days of MS-DOS and CP/M there was no memory protection, you could do stuff like this, and it usually caused terrible problems. The first PC viruses would use tricks like that to modify your program so it formatted your hard drive when you tried to run it. Commented Aug 27, 2015 at 22:03

2 Answers 2

43

Historically (perhaps by rewriting parts of it), it was the contrary. On the very first computers of the early 1970s (perhaps PDP-11) running a prototypical embryonic C (perhaps BCPL) there was no MMU and no memory protection (which existed on most older IBM/360 mainframes). So every byte of memory (including those handling literal strings or machine code) could be overwritten by an erroneous program (imagine a program changing some % to / in a printf(3) format string). Hence, literal strings and constants were writable.

As a teenager in 1975, I coded in the Palais de la Découverte museum in Paris on old 1960s era computers without memory protection: IBM/1620 had only a core memory -which you could initialize thru the keyboard, so you had to type several dozens of digits to read the initial program on punched tapes; CAB/500 had a magnetic drum memory; you could disable writing some tracks thru mechanical switches near the drum.

Later, computers got some form of memory management unit (MMU) with some memory protection. There was a device forbidding the CPU to overwrite some kind of memory. So some memory segments, notably the code segment (a.k.a. .text segment) became read-only (except by the operating system which loaded them from disk). It was natural for the compiler and the linker to put the literal strings in that code segment, and literal strings became read only. When your program tried to overwrite them, it was bad, an undefined behavior. And having a read-only code segment in virtual memory gives a significant advantage: several processes running the same program share the same RAM (physical memory pages) for that code segment (see MAP_SHARED flag for mmap(2) on Linux).

Today, cheap microcontrollers have some read-only memory (e.g. their Flash or ROM), and keep their code (and the literal strings and other constants) there. And real microprocessors (like the one in your tablet, laptop or desktop) have a sophisticated memory management unit and cache machinery used for virtual memory & paging. So the code segment of the executable program (e.g. in ELF) is memory mapped as a read-only, shareable, and executable segment (by mmap(2) or execve(2) on Linux; BTW you could give directives to ld to get a writable code segment if you really wanted to). Writing or abusing it is generally a segmentation fault.

So the C standard is baroque: legally (only for historical reasons), literal strings are not const char[] arrays, but only char[] arrays that are forbidden to be overwritten.

BTW, few current languages permit string literals to be overwritten (even Ocaml which historically -and badly- had writable literal strings has changed that behavior recently in 4.02, and now has read-only strings).

Current C compilers are able to optimize and have "ions" and "expressions" share their last 5 bytes (including the terminating null byte).

Try to compile your C code in file foo.c with gcc -O -fverbose-asm -S foo.c and look inside the generated assembler file foo.s by GCC

At last, the semantics of C is complex enough (read more about CompCert & Frama-C which are trying to capture it) and adding writable constant literal strings would make it even more arcane while making programs weaker and even less secure (and with less defined behavior), so it is very unlikely that future C standards would accept writable literal strings. Perhaps on the contrary they would make them const char[] arrays as they morally should be.

Notice also that for many reasons, mutable data is harder to handle by the computer (cache coherency), to code for, to understand by the developer, than constant data. So it preferable to have most of your data (and notably literal strings) stay immutable. Read more about functional programming paradigm.

In the old Fortran77 days on IBM/7094, a bug could even change a constant: if you CALL FOO(1) and if FOO happened to modify its argument passed by reference to 2, the implementation might have changed other occurrences of 1 into 2, and that was a really naughty bug, quite hard to find.

5
  • Is this to protect strings as constants? Even though they are not defined as const in standard (stackoverflow.com/questions/2245664/…)?
    – user173341
    Commented Aug 27, 2015 at 19:47
  • Are you sure the first computers had no read-only memory? Wasn't that considerably cheaper than ram? Also, putting them into RO-memory doesn't cause cause UB on trying to erroneously modify them, but relying on the OP not doing that and him violating that trust. See for example Fortran-programs where all literal 1s suddenly behave like 2s and such fun... Commented Aug 27, 2015 at 20:23
  • 1
    As a teenager in a museum, I coded in 1975 on old IBM/1620 and CAB500 computers. Neither had any ROM: IBM/1620 had core memory, and CAB500 had a magnetic drum (some tracks could be disabled to be writable by a mechanical switch) Commented Aug 27, 2015 at 20:25
  • 2
    Also worth pointing out: Putting literals in the code segment means they can be shared among multiple copies of the program because initialization happens at compile time rather than run time.
    – Blrfl
    Commented Aug 27, 2015 at 20:28
  • @Deduplicator Well, I've seen a machine running a BASIC variant which allowed you to change integer constants (I'm not sure if you needed to trick it into doing so, e.g. passing "byref" arguments or if a simple let 2 = 3 worked). This resulted in a lot of FUN (in the Dwarf Fortress definition of the word), of course. I have no idea how the interpreter was designed that it allowed this, but it was.
    – Luaan
    Commented Sep 4, 2015 at 12:34
3

Compilers couldn't combine "more" and "regex", because the former has a null byte after the e while the latter has an x, but many compilers would combine string literals which matched perfectly, and some would also match string literals that shared a common tail. Code which changes a string literal may thus change a different string literal which is used for some entirely different purpose but happens to contain the same characters.

A similar issue would arise in FORTRAN prior to the invention of C. Arguments were always passed by address rather than by value. A routine to add two numbers would thus be equivalent to:

float sum(float *f1, float *f2) { return *f1 + *f2; }

In the event that one wanted to pass a constant value (e.g. 4.0) to sum, the compiler would create an anonymous variable and initialize it to 4.0. If the same value was passed to multiple functions, the compiler would pass the same address to all of them. As a consequence, if a function which modified one of its parameters was passed a floating-point constant, the value of that constant elsewhere in the program could get changed as a result, thus leading to the saying "Variables won't; constants aren't".