当对联合中的 char 进行 strcpy 时程序中止

A program aborts when strcpying over a char in a union

我正在尝试 strcpy 大小为 8 的并集,如下所示:

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


typedef union {
  double num;
  char chr;
} doublechar;

int main (int argc, char *argv[])
{
  doublechar test;
  strcpy(&test, "test");
  printf("%s\n", &test);

  return 0;
}

这很好用。但是,当我尝试使用 strcpystrncpy 将字符复制到联合地址时,程序崩溃并显示 abort 消息:

strcpy(&test.chr, "test"); // this does not work
strncpy(&test.chr, "test", 3); // this does not work
strcpy(&test.num, "test"); // this works
memcpy(&test.chr, "test", 3); // this works

这四种情况,内存地址都是一样的,为什么有的会失败呢? strcpystrncpy 似乎也不适用于堆分配联合。此外,这似乎工作正常,即使它不应该:

char *p = &test.chr;
strcpy(p, "test"); // this works

谁能解释一下?

编辑: 显然,编译器在编译该程序时会产生一堆警告,但所有这些都与 printf 格式说明符有关。这是一个可以干净编译的程序版本:

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


typedef union {
  double num;
  char chr;
} doublechar;

int main (int argc, char *argv[])
{
  doublechar test;
  strcpy(&test.chr, "test");
  printf("%s\n", &test.chr);

  return 0;
}

我正在使用以下编译器:

Apple LLVM version 10.0.0 (clang-1000.11.45.5)
Target: x86_64-apple-darwin18.2.0
Thread model: posix

这是我在 运行 程序时看到的:

[1]    74379 abort      a.out

这个

strcpy(&test, "test");

不正确,如果您使用 -Wall -Wstrict-prototypes -Wpedantic -Werror 之类的标志编译代码,编译器可能会像下面这样警告您。永远不要忽略编译器警告。

error: passing argument 1 of ‘strcpy’ from incompatible pointer type [-Werror] strcpy(&test, "test"); ^

因为 &testdoublechar* 类型而 "test"char* 类型 & 将 char* 复制到 doublechar* 会导致上述错误留言。

也在这里

typedef union {
  double num; /* 8 byte gets allocated for whole union as this member needs the highest memory */
  char chr;
} doublechar;

doublechar 是联合,即这里所有成员共享共同内存,即 32-bit 系统

中的 8 字节
 --------------------------------------------------
 |                         |                        |
  --------------------------------------------------
 MSB                                           <-- LSB
                                                   num
                                                   chr <-- both num and chr access memory from beginning

还有这个

strcpy(&test.chr, "test"); // this does not work
printf("%s\n", &test); /* format specifier is wrong */

导致未定义的行为因为test.chr属于char类型,不推荐复制超过1 char因为它可能会覆盖下一个成员的内容,这样做时要小心。

另外 printf 格式说明符不正确,%s 需要 char* 的参数并且 &test 类型不是 char*。你想要像下面

strcpy(&test.chr, "t"); /* test.chr is of char type, */
printf("%c\n", test.chr); /* use %c as chr is of char type*/
printf("%p\n",(void*)&test); /* use %p if you want to print address */

也在这里

strcpy(&test.num, "test"); // this works

不,它不起作用 因为 test.numdouble 类型而不是 char* 类型,你的编译器可能会警告你喜欢

note: expected ‘char * restrict’ but argument is of type ‘double *’

在上述情况下你可能想使用 memcpy()

原因很简单。您已将 test 定义为 doublechar,因此 test.chr 单个字符 。当您指向它时,它的行为是为了索引 as if it were a pointer to the first element of an array of length 1.

在这里,

strcpy(&test.chr, "test");

您正在尝试复制长度为 5 的数组,覆盖长度为 1 的数组,但行为未定义。它是否与 &test.num 相同的地址并不重要——因为这不是唯一重要的事情;同样重要的是寻址元素的类型、元素在它所属的(可能的)数组中的位置以及指针的出处。

在过去这可能是 "non-issue",因为未定义的行为意味着一个实现超出长度为 1 的数组多 4 个字符是正确的。现在编译器和 C 实现正在内置函数中实现范围检查,并且 strcpy 可以保护您不会写出长度为 1 的 known 数组的范围,并中止在更糟糕的行为发生之前进行编程。 也是标准允许的。

未定义行为的定义是3.4.3p1

  1. undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

  2. NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).


您的程序的解决方法是清楚地说明您的意图。也许这样会更好:

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


typedef union {
    double num;
    char chrs[sizeof (double)];
} doublechar;

int main (int argc, char *argv[])
{
  doublechar test;
  strcpy(test.chrs, "test");
  printf("%s\n", test.chrs);

  return 0;
}

郑重声明,GCC Ubuntu 7.3.0-27ubuntu1~18.04 在您最后的摘录中表现得更好 - 它发出 正确的诊断 :

% gcc union.c -O3
In file included from /usr/include/string.h:494:0,
                 from union.c:2:
In function ‘strcpy’,
    inlined from ‘main’ at union.c:13:3:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:90:10: warning: 
   ‘__builtin___memcpy_chk’ writing 5 bytes into a region of size 1 overflows the 
   destination [-Wstringop-overflow=]
   return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

% ./a.out        
*** buffer overflow detected ***: ./a.out terminated
zsh: abort (core dumped)  ./a.out

在这里,仅仅使用默认开关是不够的;未经优化编译的会打印 test.