Now for this I am going to want to change the third letter "i" into an "o" to make it "Thos is some text". That could, in all cases (you would think), be achieved by:

text[2] = 'o';

Now let's look at what each way of declaring the string does and how that text[2] = 'o'; statement would affect things.

First the most commonly seen way: char *text = "This is some text";. What does this literally mean? Well, in C, it literally means "Create a variable called text which is a read-write pointer to this string literal which is held in read-only (code) space.". If you have the option -Wwrite-strings turned on then you get a warning as seen in the question above.

Basically that means "Warning: You have tried to make a variable that is read-write point to an area you can't write to". If you try and then set the third character to "o" you would in fact be trying to write to a read-only area and things won't be nice. On a traditional PC with Linux that results in:

Segmentation Fault

Now the second one: char text[] = "This is some text";. Literally, in C, that means "Create an array of type "char" and initialize it with the data "This is some text\0". The size of the array will be big enough to store the data". So that actually allocates RAM and copies the value "This is some text\0" into it at runtime. No warnings, no errors, perfectly valid. And the right way to do it if you want to be able to edit the data. Let's try running the command text[2] = 'o':

Thos is some text

It worked, perfectly. Good.

Now the third way: const char *text = "This is some text";. Again the literal meaning: "Create a variable called "text" that is a read only pointer to this data in read only memory.". Note that both the pointer and the data are now read-only. No errors, no warnings. What happens if we try and run our test command? Well, we can't. The compiler is now intelligent and knows that we are trying to do something bad:

error: assignment of read-only location ‘*(text + 2u)’

It won't even compile. Trying to write to read-only memory is now protected because we have told the compiler that our pointer is to read-only memory. Of course, it doesn't have to be pointing to read-only memory, but if you point it to read-write memory (RAM) that memory will still be protected from being written to by the compiler.

Finally the last form: const char text[] = "This is some text";. Again, like before with [] it allocates an array in RAM and copies the data into it. However, now this is a read-only array. You can't write to it because the pointer to it is tagged as const. Attempting to write to it results in:

error: assignment of read-only location ‘*(text + 2u)’

So, a quick summary of where we are:

This form is completely invalid and should be avoided at all costs. It opens the door to all sorts of bad things happening:

char *text = "This is some text";

This form is the right form if you are wanting to make the data editable:

char text[] = "This is some text";

This form is the right form if you want strings that won't be edited:

const char *text = "This is some text";

This form seems wasteful of RAM but it does have its uses. Best forget it for now though.

It is worth noting that on the Arduinos (the AVR-based ones at least), string literals live in RAM, unless you declare them with a macro like PROGMEM, PSTR() or F(). Thus, const char text[] does not use more RAM than const char *text.
– Edgar BonetJul 16 '15 at 7:35

Teensyduino and many other more recent arduino-compatibles automatically place string literals in code space, so it's worth checking to see whether or not F() is needed on your board.
– Craig.FeiedJun 12 '18 at 13:30

@Craig.Feied In general F() should be used regardless. Those that don't "need" it tend to define it as a simple (const char *)(...) casting. No real effect if the board doesn't need it, but a big saving if you then port your code to a board that does.
– Majenko♦Jun 12 '18 at 13:32

The compiler knows this is dodgy, and it is right! The reason for this is, that the compiler (reasonably) expects that string constants don't change (since they are constants). Thus if you refer to the string constant "This is some text" multiple times in your code it is allowed to allocate the same memory to all of them. Now if you modify one, you modify all of them!

The function foo expects a char* (which it can therefore modify) but you are passing a string literal, which should not be modified.

The compiler is warning you not to do this. Being deprecated it might turn from a warning into an error in a future compiler version.

Solution: Make foo take a const char *:

void foo (const char * s)
{
Serial.println (s);
}

I don't get it. Do you mean can not be modified?

Older versions of C (and C++) let you write code like my example above. You could make a function (like foo) that prints something you pass down to it, and then pass down a literal string (eg. foo ("Hi there!");)

However a function that takes char * as an argument is allowed to modify its argument (ie. modify Hi there! in this case).

You might have written, for example:

void foo (char * s)
{
Serial.println (s);
strcpy (s, "Goodbye");
}

Unfortunately, by passing down a literal, you have now potentially modified that literal so that "Hi there!" is now "Goodbye" which is not good. In fact if you copied in a longer string, you might overwrite other variables. Or, on some implementations you would get an access violation because "Hi there!" might have been put in read-only (protected) RAM.

So the compiler-writers are gradually deprecating this usage, so that functions to which you pass down a literal, must declare that argument as const.