争用联合中的字符时,程序中止

时间:2019-01-01 05:55:52

标签: c string pointers clang unions

我正在尝试将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

2 个答案:

答案 0 :(得分:3)

原因很简单。您已将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相同并不重要-因为那不是唯一重要的事情;同样重要的是被寻址元素的类型,该元素在其所属的(可能)数组中的位置以及指针的出处。

在过去,这可能是“非问题”,因为未定义的行为意味着使用另外4个字符超出长度1的数组的实现将是正确的。现在,编译器和C实现正在内置函数中实现范围检查,strcpy可以保证您不会写出长度为1的已知数组的边界,并且可以在异常恶化之前中止程序行为发生。标准也允许

未定义行为的定义为3.4.3p1

  
      
  1. 使用非便携式或错误程序构造或错误数据时未定义的行为行为,本国际   标准没有任何要求

  2.   
  3. 注意可能的不确定行为包括从完全忽略具有无法预测结果的情况到在翻译过程中的行为   或以书面形式记录程序执行   环境(带或不带诊断消息),   终止翻译或执行(签发   诊断消息)。

  4.   

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

#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

答案 1 :(得分:0)

strcpy(&test, "test");

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

  

错误:从不兼容的指针类型传递“ strcpy”的参数1   [-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是并集,即所有成员共享8系统中32-bit个字节的公用内存

 --------------------------------------------------
 |                         |                        |
  --------------------------------------------------
 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期望参数%schar*类型的参数不是&test。您想像下面一样

char*

也在这里

  

strcpy(&test.num,“ test”); //这有效

否,它不起作用,因为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 */ test.num类型而不是double类型,您的编译器可能会警告您

  

注意:预期为'char * restrict ',但参数的类型为'double   *’

在上述情况下,您可能希望使用char*