При написании проекта у меня возникла странная проблема.
Это минимальный код, который мне удалось написать, чтобы воссоздать проблему. Я намеренно сохраняю фактическую строку вместо места, где выделено достаточно места.
// #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h> // For offsetof()
typedef struct _pack{
// The type of `c` doesn't matter as long as it inside of a struct.
int64_t c;
} pack;
int main(){
pack *p;
char str[9] = "aaaaaaaa"; // Input
size_t len = offsetof(pack, c) + (strlen(str) + 1);
p = malloc(len);
// Version 1: crash
strcpy((char*)&(p->c), str);
// Version 2: crash
strncpy((char*)&(p->c), str, strlen(str)+1);
// Version 3: works!
memcpy((char*)&(p->c), str, strlen(str)+1);
// puts((char*)&(p->c));
free(p);
return 0;
}
Приведенный выше код меня путает:
- С
gcc/clang -O0
обе функцииstrcpy()
иmemcpy()
работают в Linux/WSL, а нижеputs()
дает все, что я ввел. - С
clang -O0
на OSX, код сработает сstrcpy()
. - С
gcc/clang -O2
или-O3
в Ubuntu/Fedora/WSL код вызывает (!!) вstrcpy()
, аmemcpy()
работает хорошо. - С
gcc.exe
в Windows код работает хорошо, независимо от уровня оптимизации.
Также я нашел некоторые другие черты кода:
- (Похоже) минимальный вход для воспроизведения аварии - 9 байт (включая нулевой терминатор) или
1+sizeof(p->c)
. С этой длиной (или дольше) гарантируется сбой (Дорогой я...). - Даже если я выделил дополнительное пространство (до 1 МБ) в
malloc()
, это не поможет. Вышеуказанные действия не меняются вообще. -
strncpy()
ведет себя точно так же, даже с правильной длиной, переданной его третьему аргументу. - Указатель не имеет значения. Если член структуры
char *c
изменен наlong long c
(илиint64_t
), поведение остается прежним. (Обновление: изменено уже). -
Сообщение о сбое не выглядит регулярным. Предоставляется дополнительная информация.
Я пробовал все эти компиляторы, и они не имели значения:
- GCC 5.4.0 (Ubuntu/Fedora/OS X/WSL, все 64-разрядные)
- GCC 6.3.0 (только для Ubuntu)
- GCC 7.2.0 (Android, norepro???) (Это GCC из C4droid)
- Clang 5.0.0 (Ubuntu/OS X)
- MinGW GCC 6.3.0 (Windows 7/10, оба x64)
Кроме того, эта настраиваемая функция копирования строк, которая выглядит точно так же, как стандартная, хорошо работает с любой конфигурацией компилятора, упомянутой выше:
char* my_strcpy(char *d, const char* s){
char *r = d;
while (*s){
*(d++) = *(s++);
}
*d = '\0';
return r;
}
Вопросы:
- Почему
strcpy()
не работает? Как это можно сделать? - Почему это происходит, только если включена оптимизация?
- Почему не
memcpy()
выходит из строя независимо от уровня-O
* Если вы хотите обсудить нарушение прав доступа к членству в структуре, проконсультируйтесь с здесь.
Часть выходного файла objdump -d
исполняемого файла (WSL):
P.S. Сначала я хочу написать структуру, последний элемент которой является указателем на динамически выделенное пространство (для строки). Когда я пишу struct в файл, я не могу написать указатель. Я должен написать фактическую строку. Поэтому я придумал это решение: принудительно храните строку вместо указателя.
Также не жалуйтесь на gets()
. Я не использую его в своем проекте, но только код примера.