10

对于某些人来说,这可能是一个非常基本的问题。我试图了解 strcpy 在幕后的实际工作方式。例如,在这段代码中

#include <stdio.h>
#include <string.h>
int main ()
{
  char s[6] = "Hello";
  char a[20] = "world isnsadsdas";
  strcpy(s,a);

  printf("%s\n",s);
  printf("%d\n", sizeof(s));
  return 0;
}

正如我声明s的那样,它是一个大小小于源的静态数组。我认为它不会打印整个单词,但它确实打印了world isnsadsdas.. 所以,我认为如果目标小于源,这个 strcpy 函数可能会分配新的大小。但是现在,当我检查 sizeof(s) 时,它仍然是 6,但打印出来的不止于此。实际效果如何?

4

8 回答 8

16

你刚刚导致了未定义的行为,所以任何事情都可能发生。在你的情况下,你很幸运并且它没有崩溃,但你不应该依赖这种情况。这是一个简化的strcpy实现(但它与许多真实的相差不远):

char *strcpy(char *d, const char *s)
{
   char *saved = d;
   while (*s)
   {
       *d++ = *s++;
   }
   *d = 0;
   return saved;
}

sizeof只是从编译时返回数组的大小。如果你使用strlen,我想你会看到你所期望的。但正如我上面提到的,依赖未定义的行为是一个坏主意。

于 2013-02-06T07:04:26.910 回答
5

http://natashenka.ca/wp-content/uploads/2014/01/strcpy8x11.png

strcpy 被认为是危险的,例如您正在演示的原因。您创建的两个缓冲区是存储在函数堆栈帧中的局部变量。堆栈框架大致如下:http: //upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Call_stack_layout.svg/342px-Call_stack_layout.svg.png

仅供参考,事物被放在堆栈顶部,这意味着它在内存中向后增长(这并不意味着内存中的变量被向后读取,只是新的变量被放在旧变量的“后面”)。因此,这意味着如果您在函数堆栈帧的 locals 部分写入足够多的内容,您将在要复制到的变量之后向前写入所有其他堆栈变量并进入其他部分,并最终覆盖返回指针。结果是,如果你很聪明,你可以完全控制函数返回的位置。你可以让它真正做任何事情,但问题不是你。

正如您通过使第一个缓冲区为 5 个字符的字符串长度为 6 个字符而知道的那样,C 字符串以空字节 \x00 结尾。strcpy 函数复制字节直到源字节为 0,但它不检查目标是否有那么长,这就是它可以复制数组边界的原因。这也是为什么您的打印正在读取超过其大小的缓冲区,它读取到 \x00。有趣的是,strcpy 可能已写入 s 的数据,具体取决于编译器在堆栈中给出的顺序,所以一个有趣的练习可能是打印 a 并查看是否得到类似 'snsadsdas' 的内容,但我不能确保即使它污染了 s 也会是什么样子,因为有时由于各种原因在堆栈条目之间存在字节)。

如果这个缓冲区包含一个密码来检查带有散列函数的代码,然后你将它复制到堆栈中的缓冲区,从你得到它的任何地方(如果是服务器,则为网络数据包,或文本框等)你很好可能会从源复制更多的数据,而不是目标缓冲区可以容纳的数据,并将程序的控制权交给能够向您发送数据包或尝试密码的任何用户。他们只需要输入正确数量的字符,然后输入代表地址的正确字符即可跳转到 ram 中的某个位置。

如果您检查边界并可能修剪源字符串,则可以使用 strcpy ,但这被认为是不好的做法。还有更多现代函数需要最大长度,例如http://www.cplusplus.com/reference/cstring/strncpy/

哦,最后,这都称为缓冲区溢出。一些编译器在每个堆栈条目之前和之后添加由操作系统随机选择的一小块字节。每次复制后,操作系统都会根据其副本检查这些字节,如果它们不同,则终止程序。这解决了很多安全问题,但是仍然可以将字节复制到堆栈中足够远的位置以覆盖指向函数的指针,以处理当这些字节已更改时发生的情况,从而让您做​​同样的事情。做正确的事情变得更加困难。

于 2014-12-17T19:26:37.353 回答
2

在 C 语言中,没有对数组进行边界检查,这是为了获得更好的性能而进行的权衡,而不是冒着被自己踩到脚的风险。

strcpy()不关心目标缓冲区是否足够大,因此复制太多字节会导致未定义的行为。

这就是引入新版本 strcpy 的原因之一,您可以在其中指定目标缓冲区大小strcpy_s()

于 2013-02-06T07:12:18.803 回答
1

您依赖于未定义的行为,因为编译器选择将两个数组放置在您的代码恰好可以工作的地方。这在未来可能行不通。

至于sizeof运算符,这是在编译时计算出来的。

使用足够的数组大小后,您需要使用它strlen来获取字符串的长度。

于 2013-02-06T07:05:03.587 回答
1

请注意,sizeof(s) 是在运行时确定的。使用 strlen() 查找占用的字符数。当您执行 strcpy() 时,源字符串将被目标字符串替换,因此您的输出不会是“Helloworld issadsdas”

#include <stdio.h>
#include <string.h>
main ()
{
  char s[6] = "Hello";
  char a[20] = "world isnsadsdas";
  strcpy(s,a);

  printf("%s\n",s);
  printf("%d\n", strlen(s));
}
于 2013-02-06T07:27:47.657 回答
0

了解 strcpy 如何在幕后工作的最好方法是……阅读它的源代码!您可以阅读 GLibC 的源代码:http: //fossies.org/dox/glibc-2.17/strcpy_8c_source.html。我希望它有帮助!

于 2013-02-06T07:04:15.970 回答
0

更好的解决方案是

char *strcpy(char *p,char const *q)
{
   char *saved=p;

   while(*p++=*q++);

   return saved;
}
于 2014-09-09T11:02:07.413 回答
0

在每个字符串/字符数组的末尾都有一个null terminator character '\0'标记字符串/字符数组的结尾。

strcpy()执行它的任务,直到它看到 '\0' 字符。

printf()也执行它的任务,直到它看到 '\0' 字符。

sizeof()另一方面,对数组的内容感兴趣,只对它分配的大小(它应该有多大)感兴趣,因此不考虑字符串/字符数组实际结束的位置(它实际上有多大)。

与 sizeof() 不同的是strlen(),它对字符串的实际长度(而不是应该有多长)感兴趣,因此会计算字符数,直到它到达它的末尾('\0' 字符停止(它不包括 '\0' 字符)

于 2016-07-05T09:50:39.370 回答