【问题标题】:Where C++ really stores a string if the char array that stores it is smaller than a string is?如果存储字符串的 char 数组小于字符串,C++ 真正在哪里存储字符串?
【发布时间】:2012-07-12 09:47:28
【问题描述】:

我正在测试“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' 字符?编译时会变宽吗?还是字符串存储在其他地方?

另外,我做不到:

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++ 编译器。

【问题讨论】:

  • 不要使用char数组,使用std::string
  • 该技巧称为缓冲区溢出,在许多情况下被认为是具有安全隐患的漏洞。并非每次缓冲区溢出都会导致崩溃或立即导致崩溃。
  • 行为未定义,正如其他人所说。不过,额外的字符很有可能会进入name2。您可以尝试打印。
  • 这就是为什么程序员应该学习汇编语言的基础知识:在调试器中单步执行这段代码不仅可以回答他的问题,还可以填补他理解中的其他(明显的)空白。
  • 这就是为什么 C++ 程序员应该总是更喜欢使用像字符串和向量这样的对象而不是固定大小的数组。有时您确实需要一个数组来与系统调用或其他库进行交互,但这些是例外。

标签: c++ arrays string char strlen


【解决方案1】:

将比数组大小更多的条目写入数组允许调用未定义的行为。计算机可能会将这些数据存储在任何地方,或者根本不存储。

通常,数据存储在内存中接下来发生的任何内容中。这可能是另一个变量、指令流,甚至是椅子下方炸弹的控制寄存器。

简单地说:您编写了一个缓冲区溢出错误。不要那样做。


只是为了好玩未定义的行为是 C++ 标准没有评论的行为。它可以是任何东西,因为标准对它没有任何限制。

在一种特殊情况下,这种行为将我的银行余额从 10 美元增加到 18 亿美元:http://ideone.com/35FQW

你能明白为什么那个程序会这样吗?

【讨论】:

    【解决方案2】:

    这里没有技巧:) 你在缓冲区之外的内存上写,这是一个未定义的行为

    【讨论】:

      【解决方案3】:

      name1 在内存中被赋予了一个地址。如果您向它写入 80 个字节,它将从该位置开始在内存中写入超过 80 个字节。如果有一个变量存储在 name1 的地址 + 20,那么它的数据将被您写入 name1 的 80 个字节覆盖。这正是 C/C++ 中的工作方式,这些被称为缓冲区溢出,可用于破解程序。

      【讨论】:

        【解决方案4】:

        这是典型的缓冲区溢出。这就是为什么如果你将输入放在缓冲区中,你总是应该检查输入的大小。这是正在发生的事情:

        在 C++(和 C)中,数组名只是指向数组第一个元素的指针。编译器知道数组的大小并且会做一些编译时检查。但是,在运行时,它只会将其视为 char*。

        当您执行cin &gt;&gt; name1 时,您将一个char* 传递给cincin 不知道分配的空间有多大——它只有一个指向某个内存的指针。因此,它假定您分配了足够的空间,写入所有内容,并超过了数组的末尾。这是一张图片:

        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++ 对象。

        【讨论】:

        • 数组名实际上不仅仅是指向第一个数组元素的指针,而是在大多数上下文中,数组被隐式转换为指向第一个元素的指针。
        • 感谢您的完整解释,它将对 OP 有所帮助。
        猜你喜欢
        • 1970-01-01
        • 2018-07-04
        • 1970-01-01
        • 1970-01-01
        • 2015-10-25
        • 2012-03-31
        • 1970-01-01
        • 2012-02-28
        • 1970-01-01
        相关资源
        最近更新 更多