Невозможно изменить строку C

рассмотрим следующий код.

int main(void) {
    char * test = "abcdefghijklmnopqrstuvwxyz";
    test[5] = 'x';
    printf("%sn", test);
    return EXIT_SUCCESS;
}

на мой взгляд, это должно печатать abcdexghij. Тем не менее, он просто заканчивается, ничего не печатая.

int main(void) {
    char * test = "abcdefghijklmnopqrstuvwxyz";
    printf("%sn", test);
    return EXIT_SUCCESS;
}

это, однако, работает просто отлично, так что я неправильно понял концепцию манипулирования строками C или что-то еще? Если это важно, я запускаю Mac OS X 10.6, и это 32-разрядный двоичный файл, который я компилирую.

5 ответов


на принято отвечать - это хорошо, но не совсем полный.

char * test = "abcdefghijklmnopqrstuvwxyz";

A строковый литерал относится к анонимному объекту массива типа char[N] со статической продолжительностью хранения (то есть она существует для всего выполнения программы), где N - длина строки плюс один для завершения ''. Этот объект не const, но любая попытка изменить это неопределенное поведение. (Реализация can сделать строковые литералы можно записывать, если он выбирает, но большинство современных компиляторов нет.)

объявление выше создает такой анонимный объект типа char[27], и использует адрес первого элемента этого объекта для инициализации test. Таким образом, задание test[5] = 'x' попытки изменить массив, и имеет неопределенное поведение; как правило, это приведет к сбою программы. (Инициализация использует адрес, поскольку литерал является выражением типа массива, который неявно преобразуется в большинство контекстов для указателя на первый элемент массива.)

обратите внимание, что в C++ строковые литералы на самом деле const, и вышеуказанная декларация была бы незаконной. В C или C++ лучше всего объявить test как указатель на const char:

const char *test = "abcdefghijklmnopqrstuvwxyz";

поэтому компилятор предупредит вас, если вы попытаетесь изменить массив через test.

(строковые литералы C не являются const по историческим причинам. До 1989 ANSI C стандартный,const ключевое слово не существует. Требование, чтобы он использовался в декларациях, подобных вашей, сделало бы код более безопасным, но это потребовало бы изменения существующего кода, чего комитет ANSI пытался избежать. Вы должны претендует что строковые литералы являются const, хотя это не так. Если вы используете gcc,-Wwrite-strings опция заставит компилятор рассматривать строковые литералы как const -- что делает gcc несоответствующим.)

если вы хотите иметь возможность изменять строку, которая test относится к, вы можете определить его следующим образом:

char test[] = "abcdefghijklmnopqrstuvwxyz";

компилятор смотрит на инициализатор, чтобы определить, как большой test должен быть. В этом случае test будет типа char[27]. Строковый литерал по-прежнему ссылается на анонимный объект массива, доступный только для чтения, но его значение скопировал на test. (Строковый литерал в инициализаторе, используемом для инициализации объекта массива, является одним из контекстов, в котором массив не" распадается " на указатель; другие-когда это операнд унарного & или sizeof.) Поскольку больше нет ссылок на анонимный массив, компилятор может оптимизировать его.

в этом случае test сам массив, содержащий 26 символов, которые вы указали, плюс '' Терминатор. Время жизни этого массива зависит от того, где 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 - указатель или массив.

на comp.ленг.C FAQ отлично. Раздел 8 охватывает символы и строки, а вопрос 8.5 указывает на вопрос 1.32, который касается вашего конкретного вопроса. Раздел 6 охватывает зачастую запутанные отношения между массивы и указатели.


указатели Char, определенные со значением инициализации, переходят в сегмент только для чтения. Чтобы сделать их изменяемыми, вам нужно либо создать их в куче (например, с new / malloc), либо определить их как массив.

не модифицируемые:

char * foo = "abc";

изменяемые:

char foo[] = "abc";

вы должны привыкнуть сопоставлять тип переменной с типом инициализатора. В этом случае:

const char* test = "abcdefghijklmnopqrstuvwxyz";

таким образом, вы получите ошибку компиляции, а не ошибку времени выполнения. Проворачивание уровня предупреждения компилятора до max также может помочь избежать таких ловушек. Почему это не ошибка в C, вероятно, историческая; ранние компиляторы разрешили ее и запретили, возможно, сломали слишком много существующего кода, когда язык был стандартизирован. Однако теперь операционные системы не позволяют этого, поэтому это академично.


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


Do:

 char * bar = strdup(foo);
 bar[5] = 'x';

strdup создает изменяемую копию.

и да, вы действительно должны проверить это strdup не возвращает NULL.