Сбой или "ошибка сегментации", когда данные копируются / сканируются / считываются в неинициализированный указатель
Этот вопрос предназначен для использования в качестве справочного материала по всем часто задаваемым вопросам природы:
Почему при копировании / сканировании данных по адресу, на который указывает неинициализированный указатель, возникает таинственный сбой или "ошибка сегментации"?
Например:
char* ptr;
strcpy(ptr, "hello world"); // crash here!
или же
char* ptr;
scanf("%s", ptr); // crash here!
3 ответа
Указатель - это особый тип переменной, который может содержать только адрес другой переменной. Он не может содержать никаких данных. Вы не можете "копировать / хранить данные в указателе" - это не имеет никакого смысла. Вы можете установить указатель только для указания на данные, размещенные в другом месте.
Это означает, что для того, чтобы указатель имел смысл, он всегда должен указывать на допустимое место в памяти. Например, он может указывать на память, выделенную в стеке:
{
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 1 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?
Окончательный список распространенных причин ошибок сегментации
Что такое ошибка шины?
Указатели указывают только на область памяти. Вы создали указатель, но еще не связались с ячейкой памяти.
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()
!)
Не в этом случае, но одна ситуация, которая часто встречается в начале, - это использование одинарных кавычек и попытка определить строковый литерал, например:
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
Это происходит потому, что вы не выделили память для указателя char* ptr
, В этом случае вы должны динамически распределять память для указателя.
Две функции malloc()
а также calloc()
может быть использован для dynamic memory allocation
,
Попробуйте этот код:-
char* ptr;
ptr = (char *) malloc(sizeof(char)*50); // allocate space for 50 characters.
strcpy(ptr, "hello world");
Когда использование *ptr
более не забудьте освободить память, выделенную для *ptr
. Это может быть сделано с помощью free()
функция.
free(ptr); // deallocating memory.
Размер динамически выделяемой памяти может быть изменен с помощью realloc()
,
ptr = (char *)realloc(ptr, sizeof(char)*100);// allocate space for 0 characters.
В большинстве случаев "ошибка сегментации" происходит из-за ошибки в распределении памяти или массива вне связанных случаев.
Для создания изменяемой копии строки вместо использования malloc
, strlen
а также strcpy
, в библиотеке POSIX C есть удобная функция, называемая strdup
в <string.h>
который вернет копию переданной строки с завершающим нулем с выделенной продолжительностью хранения. После использования следует отпустить указатель сfree
:
char* ptr;
ptr = strdup("hello world");
ptr[0] = 'H';
puts(ptr);
free(ptr);