Разница в том, что
char *s = "Hello world";
поместит "Hello world"
в только для чтения части памяти и сделает s
указателем на то, что делает любая операция записи в этой памяти незаконна.
Выполняя:
char s[] = "Hello world";
помещает литеральную строку в постоянную память и копирует строку в новую выделенную память в стеке. Таким образом, сделать
s[0] = 'J';
законным.