【问题标题】:Overflowing char array overwrites the exact same string every time - why?溢出的 char 数组每次都会覆盖完全相同的字符串 - 为什么?
【发布时间】:2014-05-20 10:00:14
【问题描述】:

我有以下代码显示了在字符串上使用 char 数组的危险:

int main(){
    char password[] = "SECRET";
    char msg[10], ch;
    int i = 0;

    cout << "Please enter your name:";
    while((ch = getchar()) != '\n'){
            msg[i++] = ch;
    }
    msg[i] = '\0';

    cout << "\n\nHello " << msg << endl;
    cout << "The password is " << password;
}

当我输入一个超过 16 个字符的名称(存储在 char msg[10])时,这 16 个字符之后的所有内容都会替换存储在 char password[](“秘密”)中的值。 p>

  1. 为什么会这样? (一般的好奇心
  2. 为什么是 16 个字符而不是 10 个 - 数组的大小?
  3. 为什么总是password 被覆盖,而不是其他变量或我不会立即注意到的内存的其他部分?
  4. 那么在字符串上使用 char[] 有什么好处呢?

编辑:更新了后续问题:
5. 针对 passwordmsg 声明彼此相邻的论点,我将声明块改组如下:

char password[] = "SECRET";
char ch;
int i = 0;
char msg[10];

但是,没有任何变化。
6. 针对这样的论点:msgpassword 之间的差距是 6(字节?)长,我已经多次重新编译代码,包括上面的改组。仍然,没有变化。

关于原因有什么建议吗?

【问题讨论】:

  • @DrakaSAN:删除标签,谢谢。
  • “使用char[] 比使用std::string 有什么好处?”? - 什么都没有。
  • “因为未定义的行为”。但这并不是很令人满意。 “因为变量在内存中彼此相邻,两者都是彼此相邻声明的 char 数组,这并不奇怪。

标签: c++ arrays string char


【解决方案1】:

前三个问题的答案是相同的:因为这就是编译器选择在堆栈上布置这些变量的方式。标准中的任何内容都不能保证 - 事实上,您所做的是未定义的行为 - 任何事情都可能发生。

更改编译器,甚至编译器设置,可能会发生其他事情。或不。没什么好说的。

至于 4,除了与 C 代码的互操作性,或其他需要 C 风格字符串的 API,基本上没有。

【讨论】:

  • 谢谢,有道理。我更新了我的问题,将问题 5 和 6 包括在内。你们有任何 cmets 回复吗?
  • 与 1、2 和 3 相同。编译器根据需要对堆栈进行布局。此外,如果您实际上没有使用其他变量,编译器可以完全删除它们。 (除非它们的构造函数有副作用——这里不是这样。)
  • 哇,我觉得我的控制力比我想象的要少得多。
  • 堆栈布局确实是你无法控制的。至于编译器对您的代码做了什么,如果您认为“完全控制”,"as if rule" 可能会有点吓人。 (但别担心,这一切都是为了你好。不要召唤未定义行为,你会很安全。)
【解决方案2】:

1 .在您的情况下,内存是这样存储的:

 msg                |   |i      |password
| | | | | | | | | | |1|2|3|4|5|6|S|E|C|R|E|T|\0

然后你逐步在味精上写:

 msg                |           |password
|A|Z|E|R|T|Y|U|I|O|P|1|2|3|4|5|6|S|E|C|R|E|T|\0

但如果你继续:

 msg                |           |password
|A|Z|E|R|T|Y|U|I|O|P|1|2|3|4|5|6|Q|W|E|R|T|Y|

因为 char 数组不检查长度。 (搜索溢出)。

2 . 你写在内存上,你抹掉中间的所有东西,也许是 i 或不属于你的程序的东西。

3 . 所以在覆盖密码之前需要 6 个字符。它可能是 0char 以及数百万。

4 .除非你存储一个定义的字节数组...什么都没有,这就是代码证明的点。

更新:

  1. 更改代码的位置不会更改填充、添加变量、数组或更好:使用不同的编译器,这样即使在优化之后,二进制也会更改。

  2. 重新编译不会改变生成的二进制文件,因为编译器会做同样的事情。

【讨论】:

  • 确切地说,int i 位于 3-6,因为它被放置在可除以四的地址处。 1 和 2 是填充字节。也不要忘记终止'\0'。内存可能更像|A|Z|E|R|T|Y|U|I|O|P| | |1|2|3|4|Q|W|E|R|T|Y|0|。另一个提示:如果您在 1 和点之间放置一个空格,您可以解决帖子中的格式错误。
  • 想要一个 tribble-thanks ;)。就像您更改帖子一样,我对 cme​​ts 进行了编辑。
  • 谢谢。我编辑了我的问题以包括后续行动:尽管改变了它们在代码中的位置,但这两个变量似乎仍然彼此相邻。这种不可预测性是预期的吗? i 还会介于两者之间吗?
  • @Abhi:我已经编辑以回答您的更新。填充是由编译器生成的,它在编译之前优化你的代码。事实上,我可能在也可能不在这个地方。如果你想确保移动它,你应该尝试不同的编译器,或者停用所有优化。
  • @Abhi 未指定编译器应如何布置堆栈变量。我们从所描述的效果和代码到内存布局得出结论。但是访问数组元素超出了未定义的行为范围。您的编译器可能会导致您的计算机着火或导致召唤恶魔,但它仍然符合 c++ 标准。
【解决方案3】:

您的两个数组 msgpassword 是静态的,因此已放置在堆栈上,这意味着它们彼此靠近。

具体细节取决于实现,并且可能会在编译器和优化级别之间发生变化。可能是编译器在分配内存时对堆栈进行了一些填充,msg[0]password[0] 之间存在 16 个字节的间隙。

password 每次都会被覆盖,因为它恰好在您的堆栈中高于 msg。如果您使用了不同的编译器,或者在代码中交换了它们的位置,则可能不是。堆栈上的分配方式不会在执行之间改变;它是在编译时确定的(它是静态的),而不是运行时。

请注意,原则上,编译器可以随意做任何事情!我们只能对典型的编译器行为会发生什么做出有根据的猜测。 如果您真的想知道发生了什么,您必须查看输出程序集。

std::string(用于 C++)通常比 char[] 更可取——因为它实现了边界检查并管理自己的内存,所以更安全。

【讨论】:

  • 谢谢。我编辑了我的问题以包括后续行动:尽管改变了它们在代码中的位置,但这两个变量似乎仍然彼此相邻。这种不可预测性是意料之中的吗?
  • 我并不感到惊讶。正如我所说,它可能有所作为,但显然没有。编译器只对格式良好的程序的观察行为(输出)做出承诺;给出一个荒谬(未定义)的承诺。
【解决方案4】:

1) 在数组外部写入将访问其他内容。
2) 可能对齐。
3)机会。任何事情都有可能发生。
4) 什么都没有!

【讨论】:

    猜你喜欢
    • 2021-02-12
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 2011-12-18
    • 1970-01-01
    • 2013-10-22
    相关资源
    最近更新 更多