Сбой или "ошибка сегментации", когда данные копируются / сканируются / считываются в неинициализированный указатель

Этот вопрос предназначен для использования в качестве справочного материала по всем часто задаваемым вопросам природы:

Почему при копировании / сканировании данных по адресу, на который указывает неинициализированный указатель, возникает таинственный сбой или "ошибка сегментации"?

Например:

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?
Окончательный список распространенных причин ошибок сегментации
Что такое ошибка шины?

  1. Указатели указывают только на область памяти. Вы создали указатель, но еще не связались с ячейкой памяти. strcpy хочет, чтобы вы передали два указателя (первый не должен быть постоянным), которые указывают на два символьных массива, как эта подпись:

    char * strcpy ( char * destination, const char * source );
    

    пример использования:

    char* ptr = malloc(32);  
    strcpy(ptr, "hello world");
    
    char str[32];  
    strcpy(str, "hello world");
    
  2. Вы можете попробовать следующий фрагмент кода, чтобы прочитать строку до достижения символа новой строки (* вы также можете добавить другие пробельные символы, такие как "%[^\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);
Другие вопросы по тегам