我们知道strcat()消除了对目标数组的约束,并将其与源字符串连接起来。目标数组应足够大以存储串联的结果。最近我发现,即使目标数组的大小不足以添加第二个字符串,对于小型程序,strcat()仍然可以按预期执行。我开始浏览stackoverflow,发现couple- answers来回答这个问题。我想更深入地了解在下面运行此代码时硬件层中到底发生了什么?
#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstring>
using namespace std;
int main(){
char p[6] = "Hello";
cout << "Length of p before = " << strlen(p) << endl;
cout << "Size of p before = " << sizeof(p) << endl;
char as[8] = "_World!";
cout << "Length of as before = " << strlen(as) << endl;
cout << "Size of as before = " << sizeof(as) << endl;
cout << strcat(p,as) << endl;
cout << "After concatenation:" << endl;
cout << "Length of p after = " << strlen(p) << endl;
cout << "Size of p after = " << sizeof(p) << endl;
cout << "Length of as after = " << strlen(as) << endl;
cout << "Size of as after = " << sizeof(as) << endl;
return 0;
}
运行此代码后,数组p []的长度为12,而p []的大小为6。如何在这样的数组大小上物理存储这样的长度?我的意思是,对于此数组,字节数是有限的,因此,这意味着strlen(p)函数仅查找NULL终止符,并一直计数直到找到它并忽略该数组的实际分配大小。 sizeof()函数并不真正在意数组中最后一个为空字符分配的元素是否存储了空字符。
答案 0 :(得分:6)
数组p
分配在函数堆栈帧上,因此strcat
“溢出”缓冲区p
并继续写入堆栈的其他区域-通常,它覆盖其他局部区域参数,函数返回地址等(请记住,在x86平台上,函数堆栈通常“向下”增长,即朝着较小的地址增长)。这是众所周知的“缓冲区溢出”漏洞。
strlen
不知道缓冲区的实际大小,它只是寻找0
-终止符。另一方面,sizeof
是一个编译时函数,它以字节为单位返回数组大小。
答案 1 :(得分:1)
您正在写p
以外的内容,因此程序的行为是不确定的。
虽然行为是完全未定义的,但是会发生几种常见的行为:
您将覆盖一些不相关的数据。这可能是其他局部变量,函数返回地址等。在不检查编译器为该特定程序生成的程序集的情况下,无法确切猜测将被覆盖的内容。这可能导致严重的安全漏洞,因为它可以使攻击者将自己的代码注入程序的内存空间,并让他们覆盖函数的返回地址,以使程序执行其注入的代码。
程序崩溃。如果您在数组末尾写了足够多的内容以传递内存页边界,则可能会发生这种情况。该程序可以尝试写入操作系统尚未映射到您的应用程序的物理内存的虚拟内存地址。这导致操作系统终止您的应用程序(例如,在Linux上带有SIGSEGV
)。动态分配的数组通常比函数本地的数组更经常发生这种情况。