Если char * s только для чтения, зачем их переписывать?
Мой курс научил меня, что char * s являются статическими/только для чтения, поэтому я думал, что это означает, что вы не можете редактировать их после того, как вы их определили. Но когда я запускаю:
char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit = "apple";
printf("fruit is %s\n", fruit);
Затем он компилируется и дает мне:
fruit is banana
fruit is apple
Почему? Разве я неправильно понял, что значит быть только для чтения? Извините, если это очевидно, но я новичок в кодировании, и я не могу найти ответ в Интернете.
Ответы
Ответ 1
Представленный фрагмент кода не изменяет сами литералы строки. Он изменяет только значения, сохраненные в указателе fruit
.
Вы можете представить эти строки
char* fruit = "banana";
fruit = "apple";
следующим образом
char unnamed_static_array_banana[] = { 'b', 'a', 'n', 'a', 'n', 'a', '\0' };
char *fruit = &unnamed_static_array_banana[0];
char unnamed_static_array_apple[] = { 'a', 'p', 'p', 'l', 'e', '\0' };
fruit = &unnamed_static_array_apple[0];
Эти инструкции не меняют массивы, соответствующие строковым литералам.
С другой стороны, если вы попытались написать
char* fruit = "banana";
printf("fruit is %s\n", fruit);
fruit[0] = 'h';
^^^^^^^^^^^^^^
printf("fruit is %s\n", fruit);
то есть если вы попытались изменить строковый литерал с помощью указателя, указывающего на него (для первого символа строкового литерала), то программа имела поведение undefined.
Из стандарта C (6.4.5 Строковые литералы)
7 Неизвестно, являются ли эти массивы различными, если их элементы имеют соответствующие значения. Если программа пытается изменить такой массив, поведение undefined.
Ответ 2
В вашей программе выражение "banana"
обозначает строковый литерал в образ программы, массив символов. Значение выражения имеет тип char *
или "указатель на символ". Указатель указывает на первый байт этого массива, символ 'b'
.
Ваша переменная char *fruit
также имеет тип "указатель на символ" и берет свое начальное значение из этого выражения: оно инициализируется копией указателя на данные, а не самих данных; он просто указывает на b
.
Когда вы назначаете "apple"
- fruit
, вы просто заменяете его значение указателя на другое, поэтому теперь оно указывает на другой массив литералов.
Чтобы изменить сами данные, вам понадобится выражение, например:
char *fruit = "banana";
fruit[0] = 'z'; /* try to turn "banana" into "zanana" */
В соответствии со стандартом ISO C поведение этого не определено. Он мог быть в том, что массив "banana"
доступен только для чтения, но это не требуется.
Реализации C могут делать строковые литералы доступными для записи или сделать это опцией.
(Если вы можете изменить строковый литерал, это не значит, что все в порядке. Во-первых, ваша программа по-прежнему недостаточно четко определена в соответствии с ISO C: она не переносима. Во-вторых, разрешен компилятор C для объединения литералов, которые имеют общий контент в одном хранилище.Это означает, что два вхождения "banana"
в программе на самом деле могут быть точно такими же массивами. Кроме того, строковый литерал "nana"
, встречающийся где-то в программе, может быть суффиксом из массива "banana"
, встречающегося в другом месте, другими словами, совместного использования одного и того же хранилища. Модификация литерала может иметь удивительные эффекты, модификация может появляться в других литералах.)
Также "статические" и "только для чтения" не являются синонимами. Большинство статических хранилищ в C фактически модифицируются. Мы можем создать модифицируемый статический массив символов, который содержит следующую строку:
/* at file scope, i.e. outside of any function */
char fruit[] = "banana";
Или:
{
/* in a function */
static fruit[] = "banana";
Если мы не учитываем размер массива, он автоматически выбирается из инициализирующего строкового литерала и включает в себя пространство для нулевого завершающего байта. В функции нам нужно static
поставить массив в статическую память, иначе мы получим локальную переменную.
Эти массивы могут быть изменены; fruit[0] = 'z'
- корректное поведение.
Кроме того, в этих ситуациях "banana"
не обозначает массив символов. Массив - это переменная fruit
; выражение "banana"
- это просто фрагмент синтаксиса, который указывает начальное значение массива:
char *fruit = "banana"; // "banana" is an object in program image
// initial value is a pointer to that object
char fruit_array[] = "apple"; // "apple" is syntax giving initial value
Ответ 3
Объект fruit
доступен для записи - он может быть установлен для указания на другой строковый литерал.
Строковые литералы "banana"
и "apple"
недоступны для записи. Вы можете изменить fruit
, чтобы указать на строковый литерал, но если вы это сделаете, вы не должны пытаться изменить вещь, на которую указывает fruit
:
char *fruit = "banana"; // fruit points to first character of string literal
fruit = "apple"; // okay, fruit points to first character of different string literal
*fruit = 'A'; // not okay, attempting to modify contents of string literal
fruit[1] = 'P'; // not okay, attempting to modify contents of string literal
Попытка изменить содержимое строкового литерала приводит к поведению undefined - ваш код может работать как ожидалось, или вы можете получить ошибку времени выполнения или что-то совершенно неожиданное может произойти. Для безопасности, если вы определяете переменную, указывающую на строковый литерал, вы должны объявить ее const
:
const char *fruit = "banana"; // can also be written char const *
Вы все же можете назначить fruit
для указания на разные строки:
fruit = "apple";
но если вы попытаетесь изменить то, что указывает fruit
, компилятор будет кричать на вас.
Если вы хотите определить указатель, который может указывать только на один конкретный строковый литерал, вы можете const
-qualify указатель:
const char * const fruit = "banana"; // can also be written char const * const
Таким образом, если вы попытаетесь либо написать, на что указывает fruit
, либо попытаться установить fruit
, чтобы указать на другой объект, компилятор будет кричать на вас.
Ответ 4
В основном, когда вы выполняете
char* fruit = "banana";
Вы указали указатель fruit
на первую букву "банан". Когда вы печатаете его, C в основном начинается с буквы "b" и продолжает печатать буквы до тех пор, пока они не достигнут нулевого символа \0
в конце.
Затем говоря
fruit = "apple";
Вы изменили указатель fruit
, чтобы теперь указать на первую букву "apple"
Ответ 5
Прежде всего, char*
не доступны только для чтения. char * const
есть. И они отличаются от char const *
. И буквальные строки (например, "банан" ) должны быть, но не обязательно.
char * const cpfruit = "banana";
cpfruit = "apple"; // error
char const * cpfruit = "banana";
cpfruit[0] = 'x'; // error
char * ncfruit = "banana";
ncfruit[0] = 'x'; // compile will allow, but may cause run-time error.
Ответ 6
Что ваш курс научил вас правильно!
Когда вы определили char* fruit = "banana"
, в первую очередь у вас есть fruit
как указатель на постоянный символ. 7 байтов (включая нулевое завершение) строки находятся в разделе .ro
объектного файла (название раздела, очевидно, будет меняться в зависимости от платформы).
Когда вы reset указатель char указателя на "яблоко" , он просто указал на другое место памяти в разделе только для чтения, которое содержит "яблоко"
По существу, когда вы говорите, что плод является константой, он ссылается на fruit
как указатель на память const
. Если бы вы определили его как const pointer to a const string
: -
char* const fruit = "banana";
Компилятор остановил бы вас от сброса его на "яблоко"
Ответ 7
Вы указываете свою переменную fruit
на другую строку. Вы только переписываете адрес (местоположение). Компилятор увидит вашу постоянную строку "банан" и "яблоко" и сохранит их отдельно в памяти программы. Пусть говорят, что строка "banana" поступает в ячейку памяти, расположенную по адресу 1
, а "apple" сохраняется в памяти 2
. Теперь, когда вы делаете:
fruit = "banana";
компилятор просто назначит 1
переменной fruit
, что означает, что он указывает на адрес 1
, который содержит строку banana
. Когда вы выполните:
fruit = "apple";
компилятор назначит переменную 2
fruit
, что означает, что она указывает на addess 2
, где сохраняется строка apple
.
Ответ 8
При использовании char *p="banana";
банановая строка сохраняется в ячейке памяти, доступной только для чтения. После чего при вводе p="apple";
строка apple хранится в каком-то другом месте памяти, а указатель теперь указывает на новое место в памяти.
Вы можете подтвердить это, напечатав p
сразу после каждого назначения.
#include<stdio.h>
int main(void)
{
char *p = "Banana";
printf("p contains address of string constant 'Banana' at 0x%p\n", p);
p="Apple";
printf("p contains address of string constant 'Apple' at 0x%p\n", p);
}