当对联合中的 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;
}
这很好用。但是,当我尝试使用 strcpy
或 strncpy
将字符复制到联合地址时,程序崩溃并显示 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
这四种情况,内存地址都是一样的,为什么有的会失败呢? strcpy
和 strncpy
似乎也不适用于堆分配联合。此外,这似乎工作正常,即使它不应该:
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"); ^
因为 &test
是 doublechar*
类型而 "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.num
是 double
类型而不是 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
undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International
Standard imposes no requirements
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
.
我正在尝试 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;
}
这很好用。但是,当我尝试使用 strcpy
或 strncpy
将字符复制到联合地址时,程序崩溃并显示 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
这四种情况,内存地址都是一样的,为什么有的会失败呢? strcpy
和 strncpy
似乎也不适用于堆分配联合。此外,这似乎工作正常,即使它不应该:
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"); ^
因为 &test
是 doublechar*
类型而 "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.num
是 double
类型而不是 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
undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
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
.