Почему это простое строковое назначение segfault? - PullRequest
4 голосов
/ 05 марта 2009

У меня есть следующий код:

#include <iostream>
using namespace std;
int main()
{
    char* a = "foo";
    char* b = "bar";
    a = b;
    cout << a << ", " << b << endl;
    return 0;
}

Это компилирует и работает, т.е. отпечатки bar, bar. Теперь я хотел бы продемонстрировать, что здесь происходит не копирование строки. Я хотел бы изменить b и показать, что a также изменяется. Я придумал этот простой код:

#include <iostream>
using namespace std;
int main()
{
    char* a = "foo";
    char* b = "bar";
    a = b;
    b[1] = 'u'; // ← just this line added
    cout << a << ", " << b << endl;
    return 0;
}

… но это segfaults. Зачем? Интересно, что следующая модификация работает нормально:

#include <iostream>
using namespace std;
int main()
{
    char* a = "foo";
    char b[] = "bar"; // ← declaration changed here
    a = b;
    b[1] = 'u';
    cout << a << ", " << b << endl;
    return 0;
}

Почему он не похож на предыдущий? Я предполагаю, что упускаю какое-то важное различие между стилем указателя и инициализацией строки в стиле массива.

Ответы [ 6 ]

9 голосов
/ 05 марта 2009

Вы не можете изменять строковые константы, что вы получаете, когда используете синтаксис указателя на литерал, как в первых примерах кода.

См. Также этот вопрос: Создан ли строковый литерал в c ++ в статической памяти?

6 голосов
/ 05 марта 2009

Когда вы пишете это:

char *b = "bar";

компилятор выделяет анонимную (безымянную) область памяти для хранения строкового литерала "bar". Строковые литералы не могут быть изменены, поэтому компилятор (с помощью компоновщика и операционной системы) помещает строковый литерал в часть пространства памяти работающей программы, которая защищена от записи. Когда вы пытаетесь изменить его, операционная система ловит его и вызывает ошибку сегментации вашей программы.

(Ваш код C ++, а не C, но это не имеет отношения к этому вопросу.)

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

Вы также можете показать, что 'a' было изменено, напечатав значение указателя.

#include <iostream>
using namespace std;
int main()
{
    char* a = "foo";
    char* b = "bar";
    a = b;

    cout << (void*)a << ", " << (void*)b << endl;
}

Будет напечатан адрес, на который указывают «a» и «b».
Вы должны привести к 'void *', потому что оператор << перегружен для 'char *', чтобы распечатать строку, любой другой указатель напечатает адрес. </p>

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

Когда вы пишете:

char *foo = "bar";

На самом деле происходит то, что «полоса» хранится в сегменте памяти, доступном только для чтения. Следовательно, он неизменен. Вы получаете segfault, потому что вы пытаетесь изменить сегмент только для чтения.

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

Теоретически, строковый литерал не должен быть назначен на char *, только на 'const char *'. Тогда компилятор остановит вас, прежде чем вы напишете код ошибки seg.

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

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

...