【问题标题】:getline and char issue can someone explain?有人可以解释getline和char问题吗?
【发布时间】:2019-02-05 11:04:26
【问题描述】:

为什么会这样: 我告诉程序我的字符最多有 2 个字符,对吧?

#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 个字符。

这不起作用:我告诉程序我的字符最多有 1 个字符,对吧?

#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 个字符时它不起作用?

谁能解释一下?

谢谢

【问题讨论】:

  • 这是未定义的行为。使用带有大小参数的函数时,您需要跟踪边界。使用std::string name;std::getline(std::cin, name); 可能会更好。
  • 是的,我知道如何解决这个问题,有很多方法可以做到。
  • 但我只是想知道为什么 char[1] 不起作用,而 char[2] 却起作用。谢谢你的回答!
  • 通常那里没有什么重要的东西” - 嗯,比如函数返回地址?
  • @FalcoGer C++ 不需要堆栈从任何特定地址开始,堆栈增长可以在任何方向。这里是some examples

标签: c++


【解决方案1】:

这不起作用,因为您的数组大小为 2 个字节(2 * 字符大小)。 C 和 C++ 不检查数组边界。这会导致你写到数组的末尾。

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

这是undefined behavior,很大程度上取决于堆栈的状态,而较少取决于写入数组的输入。可能发生的情况是程序崩溃,因为返回指针被覆盖并指向某个随机地址,这些地址可能在您的程序中,也可能不在您的程序中,或者如果您尝试写入程序不拥有的受保护内存,则为 segmentation fault .

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

数组是多个存储单元,它们在内存中一个接一个地放置。每个都可以包含一个字符(或您使用的任何数据类型。)

例如char name[30]; 将告诉您的编译器为字符保留 30 个单元格。 name 将被视为指向 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 个字符,因为空终止符需要 1 个额外的空间,这会填满所有 30 个单元格。在循环中超出 1 个索引或忘记空终止符是一个常见错误。这很难调试,因为它很少会导致崩溃,而只会通过覆盖变量或其中的一部分而导致细微的、最常见的不可重现的错误。

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

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

const int MAX_NAME_LEN = 30;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-12
    • 1970-01-01
    • 1970-01-01
    • 2012-05-29
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多