在 Unix 上打开优化时,strcpy()/strncpy() 在具有额外空间的结构成员上崩溃?

2024-02-04

在写项目的时候,我遇到了一个奇怪的问题。

这是我为重现问题而编写的最少代码。我故意存储一个实际的字符串来代替其他东西,并分配了足够的空间。

// #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()。我的项目中没有使用它,仅使用上面的示例代码。


你正在做的事情是未定义的行为。

编译器可以假设您永远不会使用超过sizeof int64_t对于变量成员int64_t c。所以如果你尝试写更多sizeof int64_t(aka sizeof c) on c,您的代码中将会出现越界问题。之所以如此,是因为sizeof "aaaaaaaa" > sizeof int64_t.

关键是,即使您使用分配正确的内存大小malloc(),编译器可以假设您永远不会使用超过sizeof int64_t在你的strcpy() or memcpy()称呼。因为您发送的地址是c (aka int64_t c).

TL;DR:您正在尝试将 9 个字节复制到由 8 个字节组成的类型(我们假设一个字节是一个八位字节)。 (从@Kcvin https://stackoverflow.com/users/1144624/kcvin)

如果你想要类似的东西,请使用 C99 中的灵活数组成员:

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

typedef struct {
  size_t size;
  char str[];
} string;

int main(void) {
  char str[] = "aaaaaaaa";
  size_t len_str = strlen(str);
  string *p = malloc(sizeof *p + len_str + 1);
  if (!p) {
    return 1;
  }
  p->size = len_str;
  strcpy(p->str, str);
  puts(p->str);
  strncpy(p->str, str, len_str + 1);
  puts(p->str);
  memcpy(p->str, str, len_str + 1);
  puts(p->str);
  free(p);
}

注:标准报价请参见this https://stackoverflow.com/a/47224596/7076153 answer.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Unix 上打开优化时,strcpy()/strncpy() 在具有额外空间的结构成员上崩溃? 的相关文章

随机推荐