Невозможно изменить строку C
Рассмотрим следующий код.
int main (void) { char * test = "abcdefghijklmnopqrstuvwxyz"; test [5] = 'x'; printf ("% s \ n", тест); возврат EXIT_SUCCESS; }
На мой взгляд, это должно печатать abcdexghij. Тем не менее, он просто заканчивается без печати ничего.
int main (void) { char * test = "abcdefghijklmnopqrstuvwxyz"; printf ("% s \ n", тест); возврат EXIT_SUCCESS; }
Это, однако, работает просто отлично, так что я неправильно понял концепцию манипулирования строками C или что-то еще? В случае, если это важно, я использую Mac OS X 10.6, и это 32-битный бинарный файл, который я компилирую.
5 ответов
Принятый ответ хороший, но не совсем полный.
char * test = "abcdefghijklmnopqrstuvwxyz";
Строковый литерал ссылается на объект анонимного массива типа char[N]
со статической продолжительностью хранения (то есть она существует для всего выполнения программы), где N
длина строки плюс один для завершения '\0'
, Этот объект не const
, но любая попытка изменить его имеет неопределенное поведение. (Реализация может сделать строковые литералы доступными для записи, если выберет, но большинство современных компиляторов этого не делают.)
Объявление выше создает такой анонимный объект типа char[27]
и использует адрес первого элемента этого объекта для инициализации test
, Таким образом, назначение как test[5] = 'x'
пытается изменить массив и имеет неопределенное поведение; как правило, это приведет к краху вашей программы. (При инициализации используется адрес, потому что литерал является выражением типа массива, который в большинстве контекстов неявно преобразуется в указатель на первый элемент массива.)
Обратите внимание, что в C++ строковые литералы на самом деле const
и вышеуказанная декларация будет незаконной. В C или C++ лучше объявить test
как указатель на const char
:
const char *test = "abcdefghijklmnopqrstuvwxyz";
поэтому компилятор предупредит вас, если вы попытаетесь изменить массив с помощью test
,
(Строковые литералы C не являются const
по историческим причинам. До стандарта ANSI C 1989 года const
Ключевое слово не существует. Требование его использования в объявлениях, подобных вашей, было бы сделано для более безопасного кода, но для этого потребовалось бы изменить существующий код, чего комитет ANSI пытался избежать. Вы должны делать вид, что строковые литералы const
даже если это не так. Если вы используете GCC, то -Wwrite-strings
опция заставит компилятор обрабатывать строковые литералы как const
- что делает GCC не соответствующим.)
Если вы хотите иметь возможность изменить строку, test
ссылается, вы можете определить это так:
char test[] = "abcdefghijklmnopqrstuvwxyz";
Компилятор смотрит на инициализатор, чтобы определить, насколько велик test
должно быть. В этом случае, test
будет иметь тип char[27]
, Строковый литерал все еще ссылается на анонимный объект массива, доступный только для чтения, но его значение копируется в test
, (Строковый литерал в инициализаторе, используемый для инициализации объекта массива, является одним из контекстов, в которых массив не "затухает" по указателю; другие - когда это операнд унарного &
или же sizeof
.) Поскольку дальнейших ссылок на анонимный массив нет, компилятор может его оптимизировать.
В этом случае, test
сам по себе представляет собой массив, содержащий 26 символов, которые вы указали, плюс '\0'
терминатор. Время жизни этого массива зависит от того, где test
объявляется, что может иметь или не иметь значения. Например, если вы делаете это:
char *func(void) {
char test[] = "abcdefghijklmnopqrstuvwxyz";
return test; /* BAD IDEA */
}
вызывающая сторона получит указатель на то, что больше не существует. Если вам нужно обратиться к массиву вне области, в которой test
определяется, вы можете определить его как static
или вы можете выделить его, используя malloc
:
char *test = malloc(27);
if (test == NULL) {
/* error handling */
}
strcpy(test, "abcdefghijklmnopqrstuvwxyz";
поэтому массив будет продолжать существовать, пока вы не вызовете free()
, Нестандартный strdup()
функция делает это (это определяется POSIX, но не ISO C).
Обратите внимание, что test
может быть указателем или массивом в зависимости от того, как вы его объявили. Если вы пройдете test
в строковую функцию или в любую функцию, которая принимает char*
, это не имеет значения, но что-то вроде sizeof test
будет вести себя очень по-разному в зависимости от того, test
это указатель или массив.
FAQ по comp.lang.c превосходен. Раздел 8 охватывает символы и строки, а вопрос 8.5 указывает на вопрос 1.32, в котором рассматривается ваш конкретный вопрос. Раздел 6 посвящен часто сбивающим с толку отношениям между массивами и указателями.
Указатели на символы, определенные со значением инициализации, попадают в сегмент только для чтения. Чтобы сделать их модифицируемыми, вам нужно либо создать их в куче (например, с помощью new/malloc), либо определить их как массив.
Не модифицируется:
char * foo = "abc";
Изменяемые:
char foo[] = "abc";
Строковые литералы не могут быть изменены; Лучше предположить, что это не так. Смотрите здесь для более подробной информации.
Вы должны привыкнуть сопоставлять тип переменной с типом инициализатора. В этом случае:
const char* test = "abcdefghijklmnopqrstuvwxyz";
Таким образом, вы получите ошибку компилятора, а не ошибку времени выполнения. Повышение уровня предупреждения вашего компилятора до максимума также может помочь избежать таких ловушек. Почему это не ошибка в C, вероятно, является историческим; ранние компиляторы позволяли это, и запрещение этого могло нарушить слишком много существующего кода, когда язык был стандартизирован. Однако сейчас операционные системы не позволяют этого, так что это академический.
Делать:
char * bar = strdup(foo);
bar[5] = 'x';
strdup
делает изменяемую копию.
И да, вы должны действительно проверить это strdup
не вернул NULL.