запреты в строковых литералах в C

В книге K & R на стр. 104 я натолкнулся на это утверждение:

char amessage[] = "now is the time"; //an array char *pmessage = "now is the time"; //a pointer 

Отдельные символы в массиве могут быть изменены, но amessage всегда относится к одному и тому же хранилищу. Указатель pmessage может быть впоследствии изменен, чтобы указать в другом месте, но результат не определен, если вы попытаетесь изменить содержимое строки …

Так будет ли это ошибкой, которую они имели в виду в обоих случаях?

Для массива,

 amessage[] = "allocate to another address"; //wrong? 

Для указателя,

 pmessage[0] = 'n'; //wrong? 

Я просто хочу знать, когда кто-то идет против этих правил.

Благодарю.

 /* OK, modifying an array initialized by the * elements of a string literal */ amessage[0] = 'n'; /* not OK, modifying a string literal. * String literals are non-modifiable */ pmessage[0] = 'n'; 

Обратите внимание, что в C вы не можете назначать массивы, поэтому, если вы хотите скопировать массив, используйте функцию memcpy или используйте функцию strcpy для копирования строки.

Нет ничего неправильного в использовании указателей как массивов, если только эти указатели не указывают на постоянные данные (а строковые литералы – это постоянные данные). Хотя семантически некорректно, в старые времена без защиты памяти, pmessage[0] = 'n'; фактически работали бы с непредсказуемыми результатами (например, затрагивая все вхождения одного и того же литерала в рамках программы). В современной операционной системе этого не может быть из-за защиты памяти. Строковые литералы и другие константы помещаются в так называемые разделы только для чтения исполняемого файла, и когда исполняемый файл загружается в память для создания процесса, страницы памяти, содержащие разделы только для чтения, предназначены для чтения только для чтения , т. е. любая попытка изменить их содержание приводит к сбою сегрегации.

 char amessage[] = "now is the time"; 

действительно синтаксический сахар для следующего:

 char amessage[] = { 'n','o','w',' ','i','s',' ','t', 'h','e',' ','t','i','m','e','\0' }; 

т.е. он создает массив из 16 символов и инициализирует его содержимое строкой «теперь время» (вместе с терминатором NULL).

С другой стороны

 char *pmessage = "now is the time"; 

помещает те же строковые данные где-то в данные только для чтения и присваивает свой адрес указателю pmessage . Он работает примерно так:

 // This one is in the global scope so the array is not on the stack const char _some_unique_name[] = "now is the time"; char *pmessage = _some_unique_name; 

_some_unique_name выбрано так, чтобы не столкнуться с каким-либо другим идентификатором в вашей программе. Обычно используются символы, которые не разрешены языком C, но подходят для ассемблера и компоновщика (например, такие точки, как в string.1634 ).

Вы можете изменить значение указателя – это заставит его указать что-то другое, например, на другую строку. Но вы не можете изменить адрес за именем массива, т. amessage будет всегда ссылаться на тот же массив хранения, который был выделен для него на первом месте.

Вы можете ссылаться на отдельные элементы каждой строки, используя amessage[i] или pmessage[i] но вы можете назначать только элементы amessage поскольку они находятся в памяти чтения-записи.

 char amessage[] = "now is the time"; /* ^ ^ (an array) (a string literal) */ 

Когда вы используете строковый литерал для инициализации массива таким образом, вы устанавливаете начальные значения элементов массива в значениях самого литерала. И, конечно, массиву выделяется отдельная память. И вы можете изменить его содержимое.

 char *pmessage = "now is the time"; /* ^ ^ (a pointer) (a string literal) */ 

Когда вы используете строковый литерал для инициализации указателя, вы указываете на указатель на строковый литерал. Этот строковый литерал может храниться в постоянной памяти. И поэтому не может быть изменен. Сам указатель может быть изменен.

Что действительно, а что нет?

 amessage[0] = 'n'; /* Valid. Modifying array contents. */ amessage = pmessage; /* Invalid. You cannot assign to an array. */ pmessage[0] = 'n'; /* Invalid. You're trying to modify a string literal. */ 

Но:

 pmessage = amessage; /* Valid. You're modifying a pointer. */ 

Впоследствии:

 pmessage[0] = 'n'; /* Valid. You just modified pmessage above, and it now points to modifiable memory. */ 

Наконец: в C-FAQ есть запись об этом . Это стоит прочитать.

Вдохновленный ответом оуа . Я не хотел делать для него большое редактирование.

Если вы это сделаете:

 char amessage[] = "now is the time"; //an array char *pmessage = "now is the time"; //a pointer 

Вероятно, вы действительно хотите это сделать:

 const char *pmessage = "now is the time"; //a pointer 

Когда ваша программа скомпилирована, где-то в памяти будут байты «сейчас время» (обратите внимание, что существует NULL-терминатор). Это будет в постоянной памяти. Вы не должны пытаться изменить его, если могут возникнуть странные вещи (именно то, что произойдет, будет зависеть от вашей среды и если оно хранится в памяти только для чтения или в режиме чтения-записи). Поэтому, когда K & R пытался просветить вас о том, как вы можете это делать, прагматичным способом является указание указателей на константные строки const, тогда компилятор будет жаловаться, если вы попытаетесь изменить содержимое.

 char amessage[] = "now is the time"; //an array 

Имя массива – это константа. Этот адрес массива не может быть изменен. Но содержимое массива может быть изменено.

ТАК

 amessage[0]='n';//valid and it change the first element 

НО

 amessage="hello";//if you try then it wrong as array address can not be changed 

Теперь посмотрим на часть указателя: –

 char *pmessage = "now is the time"; //a pointer 

Поскольку это указатель, он может указывать на любой адрес. Но это расположение адреса в памяти может быть модифицируемым или не может быть модифицируемым. Это может быть прочитано Только или может быть разрешено чтение чтения.

поэтому, когда вы пытаетесь изменить некоторые данные в памяти с помощью указателя pmessage, тогда зависеть от результата указательной памяти. Здесь он указывает на раздел кода, поэтому чтение не поддерживается.

так

 pmessage[0]='n';//definitely give you error