getline和char问题可以有人解释吗?

时间:2019-02-05 11:04:27

标签: c++

为什么这样做: 我告诉程序我的char最多可以有2个char,对吧?

#include <iostream>
#include <string>

using namespace std;

int main() {

    char name[2];

    cout << "Please, enter your full name: " << endl;
    cin.getline(name, 100);
    cout << "Hello, " << name << "!\n";

    return 0;
}

当我输入Albert Einstein时,它可以完美工作,但是这里有15个字符,它们怎么都可以输入到我的变量中,该变量最大应为2个字符?

但是使用getline,我告诉他将姓名关联起来,在该行中最多可以写100个字符。

这是行不通的:我告诉程序我的char将最多有1个char,对吧?

#include <iostream>
#include <string>

using namespace std;

int main() {

    char name[1];

    cout << "Please, enter your full name: " << endl;
    cin.getline(name, 100);
    cout << "Hello, " << name << "!\n";

    return 0;
}

当我输入Albert Einstein时,它不起作用,但是当我创建最多1个字符的变量名时,这似乎是逻辑。

但是使用getline,我告诉他将姓名关联起来,在该行中最多可以写100个字符。

我真正不明白的是为什么当我创建它并告诉2个字符时,它起作用了,而当我告诉了1个字符却不起作用时?

有人可以解释我吗?

谢谢

1 个答案:

答案 0 :(得分:2)

这不起作用,因为您的数组的大小为2个字节(2 * char的大小)。 C和C ++不检查数组范围。这导致您在数组末尾书写。

因为stack(请注意Wiki上的插图在内存地址方面是颠倒的)是从高内存地址到低内存地址构建的,但是数据很可能从低到高写入(取决于编译器的工作方式及其设置等),然后将您在此之前声明的其他变量,函数参数,返回指针或什至超出堆栈末尾的程序的有效范围写入到该变量中。

这是undefined behavior,在很大程度上取决于堆栈的状态,而在很大程度上取决于写入数组的输入。可能发生的情况是由于返回指针被覆盖并指向某个随机地址而导致的程序崩溃,该地址可能在程序内部,也可能不在程序内部,或者segmentation fault,如果您尝试将其写入受保护的内存中,自己的。

在最坏的情况下,您有一个buffer overflow vulnerability,攻击者可能在其中伪造输入内容,从而覆盖了堆栈上的函数返回地址,从而使程序执行跳转到目标程序所需的地址。攻击者,通常是堆栈本身(刚刚充满攻击者数据的数组)并执行它。攻击者将使用一些处理器指令来填充阵列的更多内容,以执行他想要的任何操作。虽然现代操作系统可以防止这种原始的溢出漏洞,但它不应该从一开始就发生。对于更基本的措施,例如地址随机化,也有简单的解决方法。

数组是在内存中一个接一个的存储单元的数量。每个人都可以容纳一个字符(或您使用的任何数据类型。)

例如char name[30];将告诉您的编译器为字符保留30个单元格。 name将被视为指向char对象的指针,该char对象具有数组(name[0])中第一个单元格的地址。请注意,此时,这些单元格中可能包含随机数据。然后,您通过cin.getline在这些单元格中输入字符串时,它可能看起来像这样:

['M', 'y', ' ', 'N', 'a', 'm', 'e', '\0', 'f', '&', '\0', '\0', 'y', '\0', ... 'i'] (30 characters total)

C ++和C通过第一个NULL值识别字符串的结尾。因此,此空值中的数组至少必须比最大预期字符数限制多1个空间。在这种情况下,名称的最大长度为29个字符,这是因为null终止符需要1个额外的空间,并且这将填充所有30个单元格。循环中超出1个索引或忘记空终止符是常见的错误。这很难调试,因为它很少会导致崩溃,而只能通过覆盖变量或变量的一部分而引起细微的,通常是无法再现的错误。

固定的数组大小对于可变的输入长度是不好的做法。您应该研究内存分配和处理字符串的指针。或者,您可以使用方便的std :: string对象。

另一个不好的做法是使用幻数,例如在我的示例中为30。最好用强名称定义一个常量,然后改用它。例如

const int MAX_NAME_LEN = 30;

如果您想做一些实验,可以声明一个数组,并用[\xAB, \xAB, \xAB, \xAB, ...]之类的数据模式填充它。当操作系统将无效的跳转地址扔给您时,您将在错误消息的地址(0xABABABAB)中看到数据模式(如果您只是重写了堆栈中的返回地址,而不是写入受保护的内存中)。