The first one creates a pointer that points to the string literal "hello", which is probably stored in non-writable memory in the executable image of the program. Even if it isn't, you are not allowed to modify the contents of that array.
The second one creates an automatic array1 (on the stack (usually, but that is implementation-defined)) and initialises it with the string "goodbye". It is equivalent to
char const b[] = {'g', 'o', 'o', 'd', 'b', 'y', 'e', 0};
So while "goodbye" is immutable because it is a string literal which is char const[8] and stored in non-writable memory, the array b is an automatic1 array that is immutable because you marked it const, but you could remove the const from the variable declaration to make the array's contents mutable. You are only initialising the contents of the array with the contents of the array "goodbye".
You are not allowed to modify either of them because they are both const char[], but the second one could be changed to char[] to be mutable, while the first one could not.
See this answer for more info: https://stackoverflow.com/a/9106798/726361
1 As R. Martinho Fernandes pointed out in the comments, the syntax T x[] = ... could also create a static array (not automatic but static (in the executable image usually, but that's implementation defined)) if it is at namespace scope, and it's only an automatic array otherwise.
The first one creates a pointer that points to the string literal "hello", which is probably stored in non-writable memory in the executable image of the program. Even if it isn't, you are not allowed to modify the contents of that array.
The second one creates an automatic array1 (on the stack (usually, but that is implementation-defined)) and initialises it with the string "goodbye". It is equivalent to
char const b[] = {'g', 'o', 'o', 'd', 'b', 'y', 'e', 0};
So while "goodbye" is immutable because it is a string literal which is char const[8] and stored in non-writable memory, the array b is an automatic1 array that is immutable because you marked it const, but you could remove the const from the variable declaration to make the array's contents mutable. You are only initialising the contents of the array with the contents of the array "goodbye".
You are not allowed to modify either of them because they are both const char[], but the second one could be changed to char[] to be mutable, while the first one could not.
See this answer for more info: https://stackoverflow.com/a/9106798/726361
1 As R. Martinho Fernandes pointed out in the comments, the syntax T x[] = ... could also create a static array (not automatic but static (in the executable image usually, but that's implementation defined)) if it is at namespace scope, and it's only an automatic array otherwise.
A string literal has type char const[N]; of course a name of this type may decay to a char const*.
Now:
A
chararray may be initialised by a string literal (8.5.2/1), and since you cannot otherwise copy or assign arrays, it follows that this initialisation implements a copy. You're free to do with the new, mutable array whatever you like.char str[6] = "hello";Conversely, when initialising a pointer, you're obtaining a pointer that's the result of the string literal's immutable array type decaying.
char const* str = "hello";There is no new array here. Just copying a pointer to the existing, immutable data.
I tried this:
char MS[] = "Hello"; // MUTABLE String char *IS = "Hello"; // IMMUTABLE String !! MS[0] = 'M'; IS[0] = 'I'; // Crashes!
Why is the pointer String IS immutable while the MS one isn't? Any specifics about this situation?
Videos
What are the advantages of representing strings with an immutable data strucure?
A few good points i've read:
-
Easier to compare (compare by pointer)
-
Less expensive to copy
-
String pool cuts down the use of memory
Other than that, most other arguments are about immutable objects in general, ie. thread safety, optimizations, easier to reason, etc
That said, i'm considering to represent strings internally with a Mutable data structure, essentially, the string type is an alias to []byte (dynamic size byte array or byte slice), and as such is completely mutable. And here is why:
-
Fits better with the type system (specially with generics1), since all operations on slices can be done with strings
-
Less confusion on why you can mutate an item in a slice but not on a string
-
Less runtime complexity
Additionally, since function parameters are immutable by default in my language, we can pass slices by reference, not needing a copy. If you need to mutate an argument, however, i'm still deciding, either you need to explicitly pass a pointer to the value you want to mutate or you must specify that particular parameter as mutable.
1 - There are 5 (and only 5) typeclasses in my language: anything -> comparable -> orderable -> numerical -> integer. Each typeclass has associated operators. User defined types are automatically assigned to this typeclasses if they can be ordered, compared, etc. The language has an 'append' operator: a .. b appends b to the end of a, this operation is supported by both strings and slices. If strings are not slices, then i would need yet another typeclass to represent appendables. With strings being aliases, it's trivial to write generic algorythms that work in both slices and strings:
generic ReverseInPlace over T
fn(list:&[]T) {
var start = 0,
end = (@list).length;
for start < end {
val temp <- (@list)[start];
(@list)[start] <- (@list)[end];
(@list)[end] <- temp;
start <- start + 1;
end <- end - 1;
}
}
Edit: Thank you for all your input, after careful consideration, i decided to issue a compiler warning whenever string is indexed or mutated in place. That way the user will still be aware of string encodings but the type will fit well within the type system, without creating a special case. string is an alias to []byte but with training wheels, since the compiler knows this particular set of bytes is talking about the mess that is the human language. I'm sorry if that's not the outcome you expected, but in my view it's what best fits the language.
This issue is strongly connected to the notion of what it means to be an instance of a class. In strict Object-Oriented terms, a class has an associated invariant: a predicate which always holds true on exit from a (public) method of the class. Such a notion is central in ensuring that inheritance is well-defined, for example (it's part of the Liskov Substitution Principle).
One of the most pernicious issues with Java is that it is difficult to prevent client code from breaking class invariants.
For example, consider the following 'ZipCode' class:
class ZipCode {
private String zipCode;
public ZipCode(String value){
if(!isValidZipCode(value))
throw new IllegalArgumentException();
zipCode = value;
assert(invariant());
}
public String get() { return zipCode; }
public boolean invariant() {
return isValidZipCode( zipCode );
}
}
If String were not immutable, it would be possible for a user of ZipCode to call 'get' and change the characters at any subsequent time, thereby breaking the invariant and destroying the conceptual integrity offered by the encapsulation of the ZipCode concept.
Since this kind of integrity is essential to ensuring that large systems are valid, this answer to your question really begs the wider one of:
"Why doesn't Java support an analog of C++ const, or at least offer immutable versions of more of it's library classes?"
Things like strings and dates are naturally values. In C++ terms, we expect them to have a copy constructor, an assignment operator, and an equality operator, but we never expect to take their address. Hence, we don't expect them to be individually allocated on the heap. Virtual methods make no sense.
Domain objects are naturally references. C++ ones have no copy constructor, assignment operator, or equality operator (they are equal only if identical). We can take their address and we do expect them to be heap allocated. Methods are generally virtual.
Java has no value classes, only reference ones. Values are faked with immutable objects. This is true for strings, but not, unfortunately, for dates. The mutability of Java dates has caused frequent problems, and is now deprecated. Mutable values can't be used as a basis for a hash, for example.
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.
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".
Performance
Immutability in general is bad for performance. If you concatenate many strings together in a loop, for example, the needless copying and the GC overhead (if any) will hurt performance. For this reason, many languages have a mutable string type, for example Java’s StringBuilder.
Security is better for mutable strings in some situations
When memory is freed it is not cleared. The string may stay in memory for a long time after you are done using it potentially exposing sensitive information to other processes that can snoop on your memory.
For that reason, some languages have the concept of a SecureString that is mutable so you can zero out the data when you are done using it to prevent other processes from reading it.