char. Точнее, не с самой инициализацией, а последующим использованием такого указателя. Дело было под Linux Slackware, компилятором выступал gcc.Проблема выглядела примерно так:
#include <stdlib.h>
void myfunc (char *p)
{
*p = 'X';
}
int main ()
{
char *str0 = "Hello";
char str1[] = "Hello2";
printf("str1 => %s\n", str1);
myfunc(str1);
printf("str1 => %s\n", str1);
printf("str0 => %s\n", str0);
myfunc(str0);
printf("str0 => %s\n", str0);
return 0;
}Если откомпилировать этот код и попытаться выполнить, то результат будет плачевным:
> gcc a.c
> ./a.out
str1 => Hello2
str1 => Xello2
str0 => Hello
Segmentation fault
Хм, странно... Что-то не помню я, чтобы такая проблема у меня раньше возникала. А в чем же причина?
Похоже, что проблема в разнице объявления и инициализации переменных
str0 и str1. Первое, что пришло в голову, — посмотреть, во что превращается СИшный код на этапе ассемблирования:
> gcc -S a.c
> cat a.s
Смотрим, что там получилось. Ага, вот оно:
...
LC0:
.ascii "Hello\0"
LC1:
.ascii "Hello2\0"
...
movl $LC0, -12(%ebp)
movl LC1, %eax
movl %eax, -40(%ebp)
movzwl LC1+4, %eax
movw %ax, -36(%ebp)
movzbl LC1+6, %eax
movb %al, -34(%ebp)
...
При компиляции место для обеих строк резервируется в секции
.rdata. Но при инициализации указателя str0 в память стека просто записывается адрес размещения строки "Hello" (LC0), а при инициализации str1 в стеке резервируется нужное количество байт и туда полностью копируется содержимое второй строки (LC1). Когда мы пытаемся изменить вторую строку (которая скопирована в стек), то она без проблем меняется. А при попытке изменить первую — мы лезем в сегмент данных. Вот тут-то нас и поджидает Seqmentation fault. Неплохо...Немного странно, что выражение
char *str0 = "..." в результате дает константную строку. Ну, да ладно.Интересно, что
Seqmentation fault, как правило, возникает в системах с защитой памяти. А ну-ка, проверим, что там у нас в других системах: Solaris, gcc — то же самое; Windows, MSYS, gcc — то же... Ну, что ж, все понятно. Ура.Стоп. Раз уж я полез в другие системы, может быть и компилятор другой попробовать. Что у нас там в Windows есть? Компилятор
cl.exe от Microsoft:
> cl.exe a.c
> a.exe
str1 => Hello2
str1 => Xello2
str0 => Hello
str0 => Xello
Ой... :-/ Это что такое? А где же "unexpected exception" или "memory access denied"?.. Неужели
cl генерирует принципиально другой код? Ну-ка, cl, покажи, что там у тебя в ассемблере:
...
$SG829 DB 'Hello', 00H
ORG $+2
$SG831 DB 'Hello2', 00H
ORG $+1
...
mov DWORD PTR _str0$[ebp], OFFSET FLAT:$SG829
mov eax, DWORD PTR $SG831
mov DWORD PTR _str1$[ebp], eax
mov cx, WORD PTR $SG831+4
mov WORD PTR _str1$[ebp+4], cx
mov dl, BYTE PTR $SG831+6
mov BYTE PTR _str1$[ebp+6], dl
...
Странно, но все то же самое — для первой строки в стек загоняется только указатель, а вторая полностью копируется в стек. Получается, что система тут не при чем? Все дело в компиляторе?
Пришлось лезть в MSDN и читать документацию. После недолгих поисков был найден некий ключ
/GF, который, помимо прочего, заставляет компилятор размещать строковые константы в read-only memory. "If you try to modify strings under /GF, an application error occurs." Да, это обеспечит защиту строковых "констант" от модификации, но только если указать нужный ключ компилятора.А что же
gcc? Внимательно просмотрев документацию, обнаружил раздел "Incompatibilities of GCC", первый пункт которого гласил: "GCC normally makes string constants read-only." Вот так. То есть и gcc и cl могут сгенерировать код с одинаковым поведением, но первый делает это по умолчанию, а второй — только, если указать нужную опцию.Вот тут-то и пришла в голову идея заглянуть, наконец-таки, в стандарт языка Си. Параграф 6.7.8 стандарта гласит:
"...the declarationchar *p = "abc";definespwith type 'pointer to char' and initializes it to point to an object with type 'array of char' with length 4 whose elements are initialized with a character string literal. If an attempt is made to use p to modify the contents of the array, the behavior is undefined."
Эээ... И что?
Читаем внимательней — "behavior is undefined". Именно эти три слова являются причиной такого поведения моего кода под разными компиляторами. "Поведение неопределено" — этого должно быть вполне достаточно для нормальных людей, чтобы не использовать конструкции вида
char *p = "abc".Мда... Я зачем-то провел "масштабное исследование" проблемы, которое все равно меня привело туда, куда я должен был пойти в самом начале. К Стандарту. Стыдно? Да.
В чем мораль этого рассказа? Follow the standards.