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);