在写项目的时候,我遇到了一个奇怪的问题。
这是我为重现问题而编写的最少代码。我故意存储一个实际的字符串来代替其他东西,并分配了足够的空间。
// #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's 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;
}
上面的代码让我很困惑:
- With
gcc/clang -O0
, both strcpy()
and memcpy()
适用于 Linux/WSL,并且puts()
下面给出了我输入的内容。
- With
clang -O0
on OSX,代码崩溃了strcpy()
.
- With
gcc/clang -O2
or -O3
在 Ubuntu/Fedora/WSL 上, 代码crashes (!!) at strcpy()
, while memcpy()
效果很好。
- With
gcc.exe
在 Windows 上,无论优化级别如何,代码都可以正常运行。
我还发现了代码的一些其他特征:
-
(看起来像)重现崩溃的最小输入是 9 个字节(包括零终止符),或1+sizeof(p->c)
。有了这个长度(或更长),肯定会发生崩溃(天哪......)。
-
即使我分配额外的空间(最多 1MB)malloc()
,这没有帮助。上述行为根本没有改变。
-
strncpy()
即使为第三个参数提供了正确的长度,其行为也完全相同。
-
指针似乎并不重要。如果结构体成员char *c
改为long long c
(or int64_t
),行为保持不变。 (更新:已经改变)。
-
崩溃消息看起来不正常。附带提供了许多额外信息。
我尝试了所有这些编译器,它们没有什么区别:
- GCC 5.4.0(Ubuntu/Fedora/OS X/WSL,均为 64 位)
- GCC 6.3.0(仅限 Ubuntu)
- GCC 7.2.0(Android,norepro???)(这是来自C4droid https://play.google.com/store/apps/details?id=com.n0n3m4.gcc4droid)
- 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
level??
*如果您想讨论结构成员访问冲突,请前往here /q/47224138/5958455.
Part of objdump -d
崩溃的可执行文件的输出(在 WSL 上):
附:最初我想编写一个结构体,其中最后一项是指向动态分配空间(用于字符串)的指针。当我将结构写入文件时,我无法写入指针。我必须写出实际的字符串。所以我想出了这个解决方案:强制将字符串存储在指针的位置。
也请不要抱怨gets()
。我的项目中没有使用它,仅使用上面的示例代码。