Почему strcpy вызывает ошибку сегментации с глобальными переменными?

Итак, у меня есть код C:

#include <stdio.h>
#include <string.h>

/* putting one of the "char*"s here causes a segfault */
void main() {
  char* path = "/temp";
  char* temp;
  strcpy(temp, path);
}

Это компилируется, запускается и ведет себя так, как выглядит. Однако если один или оба указателя символов объявлены как глобальные переменные, strcpy приводит к ошибке сегментации. Почему это происходит? Очевидно, в моем понимании масштаба есть ошибка.


person codemonkey    schedule 23.09.2008    source источник
comment
Поскольку я не думаю, что это на самом деле решит проблему, я просто прокомментирую, что strncpy настоятельно рекомендуется вместо strcpy.   -  person Josh Gagnon    schedule 23.09.2008
comment
Джош Ганьон: На ​​самом деле, strncpy не ставит завершающий нуль, когда длина входной строки равна ›= буферу. strcpy совершенно безопасен, если вы знаете, что буфер достаточно велик. В противном случае используйте snprintf(buffer, buffer_len, "%s", src) , так как snprintf всегда ставит нулевой терминатор (просто убедитесь, что buffer_len > 0).   -  person Joey Adams    schedule 05.08.2011
comment
@Джош: я предпочитаю strlcpy. К сожалению, glibc не поддерживает его, поэтому у меня мало шансов его использовать. Я полагаю, что всегда мог бы свернуть свою собственную реализацию и добавить ее в свою личную библиотеку заголовков функций malloc и файлов, проверяющих значение null, но меня все еще раздражает, что во многих версиях Unix она есть, а в Linux ее обычно нет.   -  person JAB    schedule 05.08.2011
comment
На самом деле, это чуть не облажало меня в последнем семестре колледжа, когда я тестировал программу для четвертой и последней оценки домашнего задания для одного из моих классов (которое я уже пропустил одно домашнее задание из-за того, что думал, что оно должно быть выполнено через три дня после него). на самом деле был) удаленно и случайно зашел на сервер Unix, а не на сервер Linux, не заметив, и скомпилировал и запустил его там просто отлично, но пришло время экзамена, я получил электронное письмо от одного из TA класса, в котором говорится, что ваша программа победила ' не запускать и не компилировать на тестовом поле [Linux].   -  person JAB    schedule 05.08.2011
comment
(К счастью, я включил строки как для strlcat, так и для strncat [такая же разница, как между strlcpy и strncpy в отношении того, как они работают и что их реализует] версий выполняемой операции, поэтому нужно было просто закомментировать одну из них. строку и раскомментировать строку после.) ...Не ожидал, что потребуется так много полей для комментариев.   -  person JAB    schedule 05.08.2011


Ответы (8)


Как упоминалось в других постах, корень проблемы в том, что temp не инициализирован. Когда она объявлена ​​как автоматическая переменная в стеке, она будет содержать любой мусор, который окажется в этой области памяти. По-видимому, для используемого вами компилятора + ЦП + ОС мусор в этом месте является допустимым указателем. Strcpy «преуспевает» в том, что он не segfault, но на самом деле он скопировал строку в какое-то произвольное место в памяти. Такого рода проблема с повреждением памяти вселяет страх в сердца программистов на C повсюду, поскольку ее чрезвычайно сложно отлаживать.

Когда вы перемещаете объявление временной переменной в глобальную область, оно помещается в раздел BSS и автоматически обнуляется. Попытки разыменовать *temp затем приводят к segfault.

Когда вы перемещаете *path в глобальную область, *temp перемещается на одно место в стеке вверх. Мусор в этом месте, по-видимому, не является допустимым указателем, поэтому разыменование *temp приводит к segfault.

person DGentry    schedule 23.09.2008
comment
Как и ожидалось, изменение порядка объявлений переменных приводит к segfault программы. Спасибо! - person codemonkey; 23.09.2008

Переменная temp не указывает ни на какое хранилище (память) и не инициализирована.

если temp объявлен как char temp[32];, тогда код будет работать независимо от того, где он объявлен. Однако есть и другие проблемы с объявлением temp с фиксированным размером, но это вопрос для другого дня.

Теперь, почему он падает, когда объявляется глобально, а не локально. Удача...

При локальном объявлении значение temp исходит из того значения, которое когда-либо могло находиться в стеке в это время. К счастью, он указывает на адрес, который не вызывает сбоя. Тем не менее, это уничтожает память, используемую кем-то другим.

При глобальном объявлении на большинстве процессоров эти переменные будут храниться в сегментах данных, которые будут использовать нулевые страницы. Таким образом, char *temp выглядит так, как если бы он был объявлен char *temp=0.

person Torlack    schedule 23.09.2008

Вы забыли выделить и инициализировать temp:

temp = (char *)malloc(TEMP_SIZE);

Просто убедитесь, что TEMP_SIZE достаточно большой. Вы также можете рассчитать это во время выполнения, а затем убедиться, что размер достаточен (должен быть как минимум strlen(path))

person terminus    schedule 23.09.2008
comment
Нет необходимости инициализировать память во время temp — об этом позаботится strcpy, даже если она просто устанавливает начальный нулевой термин. - person Serafina Brocious; 23.09.2008
comment
Кроме того, он должен быть как минимум strlen(path)+1, чтобы соответствовать нулевому термину, иначе получится Bad Things T(M). - person Serafina Brocious; 23.09.2008
comment
Он имеет в виду, что вам не нужна строка temp[0] = 0, так как strcpy() добавит для вас терминатор NULL. - person John Millikin; 23.09.2008
comment
strcpy не заботится о том, на что указывает целевая строка, если она достаточно велика, чтобы содержать результат. Значение temp[0] = 0 бессмысленно, и даже если исходная строка пуста, оно все равно будет установлено с помощью strcpy. Если вы хотите очистить всю строку, вам нужно использовать memset (или что-то еще) - person Torlack; 23.09.2008
comment
Извините, неправильно понял "strcpy" вместо "strcat". я исправил свой ответ - person terminus; 23.09.2008
comment
Нет необходимости приводить указатель, возвращаемый malloc — malloc возвращает пустой указатель, поэтому приведение не требуется. Кроме того, его приведение может навредить вам, потому что оно может скрыть проблемы с неправильным определением malloc (поскольку неявное определение malloc возвращает int) в этой строке кода. - person Daniel Papasian; 04.06.2009

Как упоминалось выше, вы забыли выделить место для temp. Я предпочитаю strdup malloc+strcpy. Он делает то, что вы хотите сделать.

person Sridhar Iyer    schedule 23.09.2008

Нет - это не работает независимо от переменных - это просто выглядит так, потому что вам (не)повезло. Вам нужно выделить место для хранения содержимого строки, а не оставлять переменную неинициализированной.

Неинициализированные переменные в стеке будут указывать на довольно случайные участки памяти. Если эти адреса окажутся действительными, ваш код растопчет все, что там было, но вы не получите ошибку (но можете получить неприятные ошибки, связанные с повреждением памяти в другом месте вашего кода).

Глобальные объекты постоянно терпят неудачу, потому что они обычно устанавливаются на определенные шаблоны, указывающие на неотображенную память. Попытка разыменовать их немедленно приводит к segfault (что лучше — если оставить это на потом, ошибку будет очень сложно отследить).

person Brian    schedule 23.09.2008

Я хотел бы переписать первый фрагмент Адама как

// Make temp a static array of 256 chars
char temp[256];
strncpy(temp, sizeof(temp), path);
temp[sizeof(temp)-1] = '\0';

Таким образом вы:

1. don't have magic numbers laced through the code, and
2. you guarantee that your string is null terminated.

Второй момент заключается в потере последнего символа вашей исходной строки, если она имеет длину >= 256 символов.

person Rob Wells    schedule 23.09.2008
comment
При использовании wchar_t это не работает. Использование sizeof работает только с символами. Я знаю, это вопрос о символах, но я сталкиваюсь с множеством ошибок, когда люди используют sizeof с wchar_t. - person Torlack; 23.09.2008

Важно отметить:
целевая строка dest должна быть достаточно большой, чтобы получить копию.
В вашей ситуации у temp нет памяти, выделенной для копирования.

Скопировано со страницы руководства strcpy:

DESCRIPTION
   The  strcpy()  function  copies the string pointed to by src (including
   the terminating '\0' character) to the array pointed to by  dest.   The
   strings  may not overlap, and the destination string dest must be large
   enough to receive the copy.
person Martin York    schedule 23.09.2008

Вы вызываете неопределенное поведение, поскольку не инициализируете переменную temp. Он указывает на случайное место в памяти, так что ваша программа может работать, но, скорее всего, произойдет segfault. Вам нужно, чтобы строка назначения была массивом или указывала на динамическую память:

// Make temp a static array of 256 chars
char temp[256];
strncpy(temp, 256, path);

// Or, use dynamic memory
char *temp = (char *)malloc(256);
strncpy(temp, 256, path);

Кроме того, используйте strncpy() вместо strcpy(), чтобы избежать переполнения буфера.

person Adam Rosenfield    schedule 23.09.2008
comment
Адам, это хорошая идея, чтобы все эти магические числа плавали вокруг? ;-) - person Rob Wells; 23.09.2008
comment
у которого все еще есть ошибки, поскольку, если исходная строка имеет длину 256, строки не имеют нулевого завершения. - person Torlack; 23.09.2008
comment
Нет необходимости приводить указатель, возвращаемый malloc — malloc возвращает пустой указатель, поэтому приведение не требуется. Кроме того, его приведение может навредить вам, потому что оно может скрыть проблемы с неправильным определением malloc (поскольку неявное определение malloc возвращает int) в этой строке кода. - person Daniel Papasian; 04.06.2009
comment
@Daniel Papasian: приведение не обязательно в C, но необходимо в C++, и обычно мне нравится делать мой код C как можно более совместимым с C++. Использование неявно определенных функций без их объявления не рекомендуется в C, и их не следует использовать в новом коде C. Я всегда компилирую с -Wall, который включает -Wimplicit-function-declaration. - person Adam Rosenfield; 04.06.2009