GCC环境下strlen与strcpy函数行为异常问题咨询及原因解析
为什么两个C程序的strcpy输出会不一样?
这是一个典型的数组越界导致未定义行为的问题,而且根源出在早已被废弃的gets函数上——咱们一步一步拆解为什么两个程序会有完全不同的输出:
核心问题:未定义行为的根源
你的两个程序里,char p[5]只分配了5个字符的空间,但你输入的shadab qureshi长度远大于5。而gets函数的致命缺陷就是完全不检查输入长度:它会把你输入的所有字符一股脑写到p的起始地址,超过p的空间后,就会覆盖栈上的其他内存(比如p1、变量k甚至main函数的返回地址)。
这种行为在C语言里被称为未定义行为——意思是编译器和系统可以随便怎么处理,出现任何结果都不奇怪,两个程序的差异正是未定义行为的不同表现。
第一个程序(未使用strlen)的行为解析
当你输入长字符串后:
p的5个空间被填满('s','h','a','d','a'),剩下的字符('b',' ','q','u','r','e','s','h','i','\0')会继续往栈上的下一块内存写(因为变量声明顺序是k→p→p1,栈一般是向下增长的,所以p之后的内存就是p1)。- 执行
strcpy(p1,p)时,p里没有字符串终止符'\0'(因为输入太长,'\0'被写到了p1的后面),所以strcpy会从p开始一直读内存,直到找到那个在p1后面的'\0'——这就导致p1被复制了完整的输入字符串。 - 但之后执行
puts(p)时,栈上的内存已经被严重覆盖,甚至可能在strcpy写p1的过程中,反过来修改了p所在的内存(栈内存是连续的,越界写的影响是连锁的),所以最终p的起始地址被改成了'e','s','h','i'加'\0',输出eshi。
第二个程序(使用strlen)的行为解析
加入k = strlen(p)后,栈内存的状态被改变:
strlen的工作逻辑是从p的起始地址开始遍历,直到找到'\0'才返回长度。但p里没有'\0',所以它会一直读栈上的内存,直到碰到某个恰好是'\0'的垃圾值(或者之前越界写的某个位置)。- 找到长度后,这个值会被赋值给
k——而k的内存位置就在p的上方(栈布局的原因),这相当于修改了栈上靠近p的一块内存。 - 当执行
strcpy(p1,p)时,此时p后面的内存(被k修改过的位置)恰好出现了'\0',所以strcpy只复制了p的前5个字符('s','h','a','d','a')加'\0',因此p1和p的输出都是shada。
关键总结 & 正确修复方案
- 永远不要再用
gets:这个函数在C11标准里已经被移除,因为它是缓冲区溢出漏洞的重灾区。改用fgets,比如:
它会自动限制输入长度,避免越界。fgets(p, sizeof(p), stdin); - 字符串操作必须保证终止符和空间足够:定义字符数组时,一定要预留足够的空间存放字符串内容+
'\0';永远不要让输入长度超过数组的大小。 - 未定义行为不可预测:这两个程序的不同输出只是未定义行为的两种表现,换个编译器、换个系统甚至换个输入时机,结果都可能不一样——不要试图去“预测”未定义行为的结果,而是要从根源避免它。
内容的提问来源于stack exchange,提问作者Sumit Agrawal
相关产品推荐
相关产品推荐

