Crash или "segmentation fault", когда данные копируются/отсканированы/прочитаны неинициализированному указателю
Этот вопрос предназначен для использования в качестве справочной информации по всем часто задаваемым вопросам природы:
Почему я получаю таинственный крах или "ошибку сегментации", когда я копирую/сканирую данные на адрес, на который указывает неинициализированный указатель?
Например:
char* ptr;
strcpy(ptr, "hello world"); // crash here!
или
char* ptr;
scanf("%s", ptr); // crash here!
Ответы
Ответ 1
Указатель - это специальный тип переменной, который может содержать только адрес другой переменной. Он не может содержать никаких данных. Вы не можете "копировать/хранить данные в указателе" - это не имеет никакого смысла. Вы можете установить указатель только для указания на данные, расположенные в другом месте.
Это означает, что для того, чтобы указатель имел смысл, он всегда должен указывать на допустимое место в памяти. Например, он может указывать на память, выделенную в стеке:
{
int data = 0;
int* ptr = &data;
...
}
Или память динамически выделяется в куче:
int* ptr = malloc(sizeof(int));
Использование указателя перед его инициализацией всегда является ошибкой. Это еще не указывает на действительную память.
Все эти примеры могут привести к сбоям программы или другим непредвиденным действиям, таким как "ошибки сегментации":
/*** examples of incorrect use of pointers ***/
// 1.
int* bad;
*bad = 42;
// 2.
char* bad;
strcpy(bad, "hello");
Вместо этого вы должны убедиться, что указатель указывает на (достаточно) выделенную память:
/*** examples of correct use of pointers ***/
// 1.
int var;
int* good = &var;
*good = 42;
// 2.
char* good = malloc(5 + 1); // allocates memory for 5 characters *and* the null terminator
strcpy(good, "hello");
Обратите внимание, что вы также можете установить указатель так, чтобы он указывал на четко определенное "нигде", позволяя ему указывать на NULL
. Это делает его нулевым указателем, который гарантированно не указывает ни на одну допустимую память. Это отличается от того, что указатель остается неинициализированным.
int* p1 = NULL; // pointer to nowhere
int* p2; // uninitialized pointer, pointer to "anywhere", cannot be used yet
Тем не менее, если вы попытаетесь получить доступ к памяти, на которую указывает нулевой указатель, вы можете столкнуться с такими же проблемами, как и при использовании неинициализированного указателя: сбои или ошибки сегментации. В лучшем случае ваша система замечает, что вы пытаетесь получить доступ к нулевому адресу, а затем выдает "исключение нулевого указателя".
Решение для ошибок исключений нулевого указателя такое же: вы должны установить указатель так, чтобы он указывал на допустимую память, прежде чем ее использовать.
Дальнейшее чтение:
Указатели, указывающие на неверные данные
Как получить доступ к локальной переменной из другой функции с помощью указателей?
Можно ли получить доступ к памяти локальной переменной за пределами ее области видимости?
Ошибка сегментации и причины
Что такое ошибка сегментации?
Почему я получаю ошибку сегментации при записи в строку, инициализированную " char * s " но не " char s [] "?
В чем разница между char s [] и char * s?
Окончательный список распространенных причин ошибок сегментации
Что такое ошибка шины?
Ответ 2
-
Указатели указывают только на ячейку памяти. Вы создали указатель, но вы еще не привязались к ячейке памяти. strcpy
хочет, чтобы вы передали два указателя (первый не должен быть постоянным), которые указывают на два массива символов, таких как эта подпись:
char * strcpy ( char * destination, const char * source );
использование образца:
char* ptr = malloc(32);
strcpy(ptr, "hello world");
char str[32];
strcpy(str, "hello world");
-
Вы можете попробовать следующий фрагмент кода для чтения строки до появления символа новой строки (* вы также можете добавить другие символы пробела, такие как "%[^\t\n]s"
(вкладка, новая строка) или "%[^ \t\n]s"
(пробел, табуляция, новая строка)).
char *ptr = malloc(32);
scanf("%31[^\n]", ptr);
(В реальной жизни не забудьте проверить возвращаемое значение из scanf()
!)
Ответ 3
Это происходит потому, что у вас не выделено памяти для указателя char* ptr
.
В этом случае вы должны динамически распределять память для указателя.
Две функции malloc()
и calloc()
могут использоваться для dynamic memory allocation
.
Попробуйте этот код: -
char* ptr;
ptr = malloc(50); // allocate space for 50 characters.
strcpy(ptr, "hello world");
При использовании *ptr
не забудьте освободить память, выделенную для *ptr
. Это можно сделать с помощью функции free()
.
free(ptr); // deallocating memory.
Размер динамически выделяемой памяти можно изменить с помощью realloc()
.
char *tmp = realloc(ptr, 100); // allocate space for 100 characters.
if (! tmp) {
// reallocation failed, ptr not freed
perror("Resize failed");
exit(1);
}
else {
// reallocation succeeded, old ptr freed
ptr = tmp;
}
В большинстве случаев "ошибка сегментации" возникает из-за ошибки в распределении памяти или массива вне связанных случаев.
Ответ 4
Не в этом случае, но одна ситуация, которая часто возникает в начале, это когда используются одинарные кавычки и пытаются определить строковый литерал, например:
char ptr[5];
strcpy(ptr, 'hello'); // crash here!
^ ^ // because of ' instead "
В C 'h' представляет собой односимвольный литерал, в то время как "h" представляет собой строковый литерал, содержащий 'h' и нулевой терминатор /0 (то есть массив из 2 символов). Кроме того, в C типом символьного литерала является то, что sizeof 'h' равен 4 (на 32-битной), а sizeof (char) равен 1.
char h = 'h';
printf("Size: %d\n",sizeof(h)); //Size: 1
printf("Size: %d\n",sizeof('h')); //Size: 4
Ответ 5
Для создания изменяемой копии строки вместо использования malloc
, strlen
и strcpy
библиотека POSIX C имеет удобную функцию под названием strdup
в <string.h>
, которая будет возвращать копию переданного в строка с нулевым символом в конце с выделенной продолжительностью хранения. После использования указатель должен быть освобожден с помощью free
:
char* ptr;
ptr = strdup("hello world");
ptr[0] = 'H';
puts(ptr);
free(ptr);