C 中的 char 和 strcpy

Char and strcpy in C

我遇到了一部分问题,我得到了一个输出,但我需要解释为什么它是真实的并且有效?

char arr[4]; 
strcpy(arr,"This is a link"); 
printf("%s",arr);

当我编译和执行时,我得到以下输出。 输出:

This is a link

您的代码存在一些内存问题(缓冲区溢出)。函数 strcpy 复制字节直到空字符。函数 printf 打印直到空字符。

无法保证这段代码的行为。 就像:你告诉我"I'll pick you up at 5:00 p.m.",你来了我就在(保证)。但我不能保证我是否给你拿了一杯咖啡,因为你没有告诉我你想要一杯。也许我很好,买了两杯咖啡,或者也许我是个吝啬鬼,只给自己买了一杯。

可能有用。它可能不会。它可能会立即明显地失败。它可能会在未来的某个任意时间以微妙的方式失败,这会让你发疯。

这就是未定义行为的阴险本质。别这样。

如果它能正常工作,那完全是偶然的,我们无法保证。 可能您正在覆盖堆栈或其他内存中的内容(取决于实现和how/where实际变量str已定义(a)) 但在那之后不会使用被覆盖的内存(考虑到代码的简单性)。

它可能以 no 方式意外工作,这是一个好主意。


对于我们当中的语言律师,C11 的第 J.2 节(未定义行为的实例)明确指出:

An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]).

那个 信息性 部分引用了 6.5.6,这是规范性的,并且在讨论 pointer/integer 添加时指出(其中 a[b] 是一个示例):

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.


(a) 例如,在我的系统上,在 main 中声明变量会导致程序崩溃,因为缓冲区溢出会破坏 return堆栈上的地址。

但是,如果我将声明放在文件级别(main 之外),似乎 运行 就可以了,打印消息然后退出程序。

但我向你保证,这只是因为你丢弃的内存对于程序的继续运行并不重要在这种情况下。它几乎肯定在任何其他方面都很重要比这个例子更重要。

为什么它(当时)起作用的简短回答是——你很幸运。超出数组末尾的写入是未定义的行为未定义的行为就是这样,未定义,它很容易导致分段错误,因为它确实产生了输出。 (尽管通常会导致堆栈损坏)

在 C 中处理字符数组时,有责任确保分配了足够的存储空间。当您打算将数组用作字符串时,还必须为 每个字符 +1 分配足够的存储空间 nul-terminating 字符(这是 C 中 nul-terminated 字符串的定义)。

为什么有效?通常,当您请求说 char arr[4]; 时,编译器仅保证您为 arr 分配了 4 个字节。但是,根据编译器、对齐方式等,编译器实际上可能会将其用作 最小分配单元 的任何内容分配给 arr。这意味着虽然您只请求了 4-bytes 并且只保证有 4-usable-bytes,但编译器实际上可能已经预留了 8, 16, 32, 64, or 128, etc-bytes.

或者,再一次,你很幸运,arr 是最后一个请求的分配,并且还没有请求或写入内存中从 arr 之后的 byte-5 开始的内存地址.

关键是,您请求了 4-bytes,并且只能保证有 4-bytes 可用。是的,在你的代码中发生任何其他事情之前,它可能会在那个 printf 中工作,但是你的代码完全不可靠,你正在玩俄罗斯轮盘赌堆栈损坏(如果它还没有发生的话)。

在 C 中,您有责任确保您的代码、存储和内存使用都是 明确定义的,并且您不会误入 undefined,因为如果你这样做,所有的赌注都会被取消,你的代码不值得它存储的字节数。

如何使您的代码定义明确?适当限制和验证代码中的每个必需步骤。对于您的代码段,您可以使用 strncpy 而不是 strcpy,然后在调用 printf 之前肯定地 nul-terminate arr,例如

char arr[4] = "";                           /* initialize all values */
strncpy(arr,"This is a link", sizeof arr);  /* limit copy to bytes available */
arr[sizeof arr - 1] = 0;                    /* affirmatively nul-terminate   */
printf ("%s\n",arr);

现在,您可以在剩余的代码中依赖 arr 的内容。

只要 printf 紧跟在 strcpy 之后,您的代码就始终有效。但这是错误的编码 尝试以下,它不会工作 int j; char arr[4]; int i; strcpy(arr,"This is a link"); i=0; j=0; printf("%s",arr); 要理解为什么会这样,您必须了解堆栈的概念。所有局部变量都分配在堆栈上。因此在你的代码中,程序控制为 "arr" 分配了 4 个字节,当你复制一个大于 4 个字节的字符串时,你就是 overwriting/corrupting 一些其他内存。但是当您在 strcpy 之后访问 "arr" 时,您覆盖的区域可能属于一些其他变量仍然没有被程序更新,这就是为什么您的 printf 工作正常。但是正如我在示例代码中建议的那样,其他变量已更新,这些变量落入您已覆盖的内存区域,您将无法获得正确的(?或需要更合适的)输出 您的代码也可以正常工作,因为堆栈向下增长,如果它本来是其他方式,那么您也没有获得所需的输出