Допустимо ли изменение строки, на которую указывает указатель? - PullRequest
1 голос
/ 03 марта 2009

Вот простой пример программы, которая объединяет две строки.

#include <stdio.h>

void strcat(char *s, char *t);

void strcat(char *s, char *t) {
    while (*s++ != '\0');
    s--;
    while ((*s++ = *t++) != '\0');
}

int main() {
    char *s = "hello";
    strcat(s, " world");
    while (*s != '\0') {
        putchar(*s++);
    }
    return 0;
}

Мне интересно, почему это работает. В main () у меня есть указатель на строку «привет». Согласно книге K & R, изменение такой строки является неопределенным поведением. Так почему же программа способна изменить его, добавив «мир»? Или добавление не считается изменением?

Ответы [ 9 ]

19 голосов
/ 03 марта 2009

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

4 голосов
/ 03 марта 2009

Я + 1 MSN, но что касается того, почему это работает, это потому, что еще ничего не пришло, чтобы заполнить пространство за вашей строкой еще. Объявите еще несколько переменных, добавьте некоторую сложность, и вы начнете видеть некоторую причудливость.

2 голосов
/ 03 марта 2009

Возможно, что удивительно, ваш компилятор выделил литерал "hello" в инициализированные данные для чтения / записи вместо инициализированных данных только для чтения. Ваше задание перекрывает все, что находится рядом с этим местом, но ваша программа небольшая и достаточно простая, чтобы вы не видели эффектов. (Поместите его в цикл for и посмотрите, не забиваете ли вы литерал " world".)

В Ubuntu x64 происходит сбой, поскольку gcc помещает строковые литералы в данные только для чтения, а при попытке записи - аппаратные объекты MMU.

1 голос
/ 03 марта 2009

Согласно спецификации C99 (C99: TC3, 6.4.5, §5) строковые литералы имеют значение

[...] используется для инициализации массива статической длительности и длины хранилища просто достаточно, чтобы содержать последовательность. [...]

, что означает, что они имеют тип char [], т.е. в принципе возможна модификация. Почему вы не должны этого делать, объясняется в §6:

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

Различные строковые литералы с одинаковым содержимым могут, но не обязательно, отображаться в одну и ту же ячейку памяти. Поскольку поведение не определено, компиляторы могут свободно размещать их в разделах, предназначенных только для чтения, для того, чтобы избежать ошибок, вместо того, чтобы вводить, возможно, трудно обнаруживаемые источники ошибок.

1 голос
/ 03 марта 2009

Мне интересно, почему это работает

Это не так. Это вызывает ошибку сегментации в Ubuntu x64; чтобы код работал, он не должен просто работать на вашем компьютере .

Перемещение измененных данных в стек позволяет обойти защиту области данных в linux:

int main() {
    char b[] = "hello";
    char c[] = " ";
    char *s = b;

    strcat(s, " world");

    puts(b);
    puts(c);

    return 0;
}

Хотя тогда вы в безопасности только тогда, когда «мир» помещается в неиспользуемые пробелы между данными стека - измените b на «привет» и linux обнаружит повреждение стека:

*** stack smashing detected ***: bin/clobber terminated
1 голос
/ 03 марта 2009

Это также зависит от того, как указатель объявлен. Например, может изменить ptr, и на что указывает ptr:

char * ptr;

Может изменить то, на что указывает ptr, но не ptr:

char const * ptr;

Может изменить ptr, но не то, на что указывает ptr:

const char * ptr;

Ничего не могу изменить:

const char const * ptr;
1 голос
/ 03 марта 2009

На этот раз вам повезло.
Особенно в режиме отладки некоторые компиляторы помещают запасную память (часто заполненную каким-то очевидным значением) вокруг объявлений, чтобы вы могли найти такой код.

0 голосов
/ 03 марта 2009

s указывает на часть памяти, которая содержит «привет», но не должна была содержать больше, чем это. Это означает, что очень вероятно, что вы будете перезаписывать что-то еще. Это очень опасно, хотя может показаться, что оно работает.

Два наблюдения:

  1. * in * s-- не обязательно. s-- будет достаточно, потому что вы хотите только уменьшить значение.
  2. Вам не нужно писать strcat самостоятельно. Он уже существует (вы, наверное, знали это, но я все равно говорю вам: -)).
0 голосов
/ 03 марта 2009

Компилятор позволяет вам изменять s, потому что вы неправильно пометили его как неконстантный - указатель на статическую строку, подобный этому, должен быть

const char *s = "hello";

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...