您好。这就是难题。我有这段代码:
#include<stdio.h>
#include<conio.h>
#include<string.h>
int main(){
char a[5];
char b[5];
memset(a, 0, 5);
memset(b, 0,5);
strcpy(a, "BANG");
printf("b = ");
scanf("%s", &b);
printf("a = %s\n", a);
getch();
}
当您运行它时,您会注意到如果您在b
中读取足够长的字符串,a
的值也会发生变化。你会期望它保持&#34; BANG&#34;,但事实并非如此。我想对此有一个解释。谢谢!
答案 0 :(得分:2)
您正在创建“缓冲区溢出”。该数组的大小只能容纳5个字节(4个字符加上标准的C字符串终结符),如果你把它放在那里,那么剩下的就会溢出。
通常情况下,重要的是让程序崩溃。
有自动工具(例如valgrind
)来检测此类错误。
答案 1 :(得分:2)
如果字符串足够长,则会出现缓冲区溢出并且行为未定义,包括覆盖其他阵列甚至导致应用程序崩溃。因为行为是未定义的,所以应该避免使用它,但只是为了理解,编译器在内存中的a
数组之后(在编译器的这个特定运行中)布置了b
数组。当您写b+sizeof(b)
时,您正在写信a[0]
。
答案 2 :(得分:2)
恭喜你,你遇到了第一次缓冲区溢出(首先你知道:))。
数组将在程序的堆栈中分配,这些数组是相邻的。由于C不检查数组边界的违反,因此您可以将任何允许的内存部分作为任何数组的单元格进行访问。
让我们回顾一个非常常见的运行时示例,该程序在x86上运行。 x86上的堆栈增长到最少地址,因此编译器通常将a[]
放在堆栈上的b[]
之上。当您尝试访问b[5]
时,其地址与a[0]
相同,b[6]
为a[1]
,依此类推。
这就是缓冲区溢出漏洞的工作原理:一些粗心的程序员不检查缓冲区中的字符串大小,然后一个邪恶的黑客将他的恶意代码写入堆栈并运行它。
答案 3 :(得分:2)
根据程序的内存来考虑它。
a
是一个包含5个字符的数组,b
是一个包含5个字符的数组。堆栈上有这样的东西:
[0][0][0][0][0][0][0][0][0][0]
^ ^
| +--"a" something like 0xbfe69e52
+-----------------"b" something like 0xbfe69e4d
所以当你做“bang”的strcpy:
[0][0][0][0][0][B][A][N][G][0]
^ ^
| +--"a" something like 0xbfe69e52
+-----------------"b" something like 0xbfe69e4d
现在,如果您将“长”字符串放入b
:
[T][h][i][s][I][s][l][o][n][g]
^ ^
| +--"a" something like 0xbfe69e52
+-----------------"b" something like 0xbfe69e4d
Opps,刚丢失a
。这是一个“缓冲区溢出”,因为您溢出b
(在这种情况下为a
)。 C不会阻止你这样做。
答案 4 :(得分:1)
上面的每个人似乎忘记提及的一件事是,堆栈通常与您期望的方向相反。
有效地分配&#39; a&#39;减去当前堆栈指针的5个字节(x86 / x64上的esp / rsp)。分配&#39; b&#39;然后再减去5个字节。
因此,当您进行第一次堆栈分配时,假设您的esp为0x1000。这给出了一个&#39;内存地址0xFB。 &#39; B&#39;然后将得到0xF6,因此0xF6的第6个字节(即索引5)是0xF6 + 5或0xFB,因此您现在正在写入数组中的一个。
这可以通过以下代码(假设32位)轻松确认:
printf( "0x%08x\n", a );
printf( "0x%08x\n", b );
您将看到b的内存地址低于。
答案 5 :(得分:0)
C对内存访问没有边界检查,因此您可以自由地读取和写入数组的声明结尾。 a
和b
可能会在内存中相邻,即使按照其声明的相反顺序排列也是如此,因此除非您的代码注意不以读取比例如更多的字符。属于b
,您可以损坏a
。实际发生的事情是未定义的,并且可能会在不同的运行中发生变化。
在这种特殊情况下,请注意您可以使用格式字符串中的宽度限制scanf
读取的字符数:scanf("%4s", &b);
答案 6 :(得分:0)
b只有5个字母。因此,如果你写一个更长的字符串,你正在写入与b相邻的内存。