我正在从“C ++ Premiere”一书中测试一个关于C ++中字符串的例子。
const int size = 9;
char name1[size];
char name2[size] = "C++owboy"; // 8 characters here
cout << "Howdy! I'm " << name2 << "! What's your name?" << endl;
cin >> name1; // I input "Qwertyuiop" - 11 chars. It is more than the size of name1 array;
// now I do cout
cout << "Well, your name has " << strlen(name1) << " letters"; // "Your name has 11 letters".
cout << " and is stored in an array of " << size(name1) << " bytes"; // ...stored in an array of 9 bytes.
如何将11个字符存储在一个数组中,仅用于8个字符+'\ 0'char?它在编译时变宽了吗?或者字符串存储在其他地方?
另外,我不能这样做:
const int size = 9;
char name2[size] = "C++owboy_12345"; // assign 14 characters to 9 chars array
但可以做我上面写的:
cin >> name1; // any length string into an array of smaller size
这里的诀窍是什么?我使用NetBeans和Cygwin g ++编译器。
答案 0 :(得分:9)
将更多条目写入数组而不是数组大小允许调用未定义行为。计算机可能会将数据存储在任何位置,或者根本不存储它。
通常,数据存储在内存中接下来发生的任何事情中。这可能是另一个变量,一个指令流,甚至是你椅子下方炸弹的控制寄存器。
简单地说:你已编写了一个缓冲区溢出错误。不要那样做。
<小时/> 只是为了好玩:未定义的行为是C ++标准不评论的行为。它可以是任何东西,因为标准没有对它施加任何限制。
在一个特定情况下,该行为将我的银行余额从10美元增加到18亿美元:http://ideone.com/35FQW
你能看出为什么那个程序可能会这样吗?
答案 1 :(得分:5)
name1在内存中被赋予一个地址。如果你写80个字节,它将从该位置开始在内存中写入超过80个字节。如果存在一个存储在name1的地址+20的变量,那么它将通过向name1写入80个字节来覆盖其数据。这就是C / C ++中的工作方式,这些被称为缓冲区溢出,可用于破解程序。
答案 2 :(得分:3)
这里没有技巧:)你正在缓冲区之外写入内存,这是一个未定义的bahaviour
答案 3 :(得分:3)
这是典型的缓冲区溢出。这就是为什么你总是应该检查输入的大小,如果你把它放在缓冲区。以下是发生的事情:
在C ++(和C)中,数组名称只是指向数组第一个元素的指针。编译器知道数组的大小,并将进行一些编译时检查。但是,在运行时,它只会将其视为char *。
当您执行cin >> name1
时,您将char *传递给cin
。 cin
不知道分配的空间有多大 - 所有它都是指向某个内存的指针。因此,它假设您分配了足够的空间,写入所有内容,并超过了数组的末尾。这是一张图片:
Bytes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Before |-----name1 array-------| |--- other data-|
After Q w e r t y u i o p \0 |-er data-|
如您所见,您已覆盖在阵列之后存储的其他数据。有时这个其他数据只是垃圾,但有时它很重要,可能意味着一个棘手的错误。更不用说,这是一个安全漏洞,因为攻击者可以用用户输入覆盖程序内存。
关于大小的混淆是因为strlen
将计算字节,直到找到'\0'
(空终止符),这意味着它找到10个字符。另一方面,size(name1)
使用编译器提供的数组的实际大小。
由于这些问题,每当您看到一个以数组作为参数的C函数时,它也会占用数组大小。否则无法分辨出它有多大。为了避免这些问题,使用像std :: string这样的C ++对象要好得多。