【问题标题】:Storing a string in an array of chars without the null character将字符串存储在不带空字符的字符数组中
【发布时间】:2013-10-21 15:50:09
【问题描述】:

我正在阅读 Stephen Prata 的 C++ Primer Plus。他举了这个例子:

char dog[8] = { 'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // not a string!
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // a string!

评论说:

这两个数组都是char的数组,但只有第二个是字符串。空字符 在 C 风格的字符串中起着基本的作用。例如,C++ 有许多函数 处理字符串,包括 cout 使用的字符串。它们都通过处理字符串字符来工作 - 逐个字符,直到它们到达空字符。如果您要求 cout 显示一个漂亮的字符串 与前面示例中的 cat 一样,它显示前七个字符,检测 null 字符,然后停止。但是如果你不礼貌地告诉 cout 显示狗数组 在前面的例子中,它不是一个字符串,cout 打印出字符串中的八个字母 数组,然后逐个字节地在内存中前进,将每个字节解释为 要打印的字符,直到它到达一个空字符。因为空字符,实际上是 字节设置为零,往往在内存中很常见,损坏通常很快被包含; 但是,您不应将非字符串字符数组视为字符串。

现在,如果将我的变量声明为全局变量,如下所示:

#include <iostream>
using namespace std;

char a[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
char b[8] = {'1', '2', '3', '4', '5', '6', '7', '8'};

int main(void)
{
    cout << a << endl;
    cout << b << endl;

    return 0;
}

输出将是:

abcdefgh12345678
12345678

因此,确实,cout “继续逐字节地在内存中前进”,但只到第二个字符数组的末尾。 char 数组的任何组合都会发生同样的事情。我在想所有其他地址都初始化为 0,这就是 cout 停止的原因。这是真的?如果我这样做:

for (int i = 0; i < 100; ++i)
{
    cout << *(&a + i) << endl;
}

我在输出时获得了大部分空白空间(可能是 95%),但不是到处都是。

但是,如果我声明我的 char 数组有点短,例如:

char a[3] = {'a', 'b', 'c'};
char b[3] = {'1', '2', '3'};

保持所有其他事情相同,我得到以下输出:

abc
123

现在 cout 甚至没有通过第一个 char 数组,更不用说第二个了。为什么会这样?我检查了内存地址,它们是连续的,就像在第一个场景中一样。例如,

cout << &a << endl;
cout << &b << endl;

给了

003B903C
003B9040

为什么在这种情况下行为不同?为什么它不读取第一个字符数组之外的内容?

最后,如果我确实在 main 中声明了我的变量,那么我确实得到了 Prata 建议的行为,即之前打印了很多垃圾,在某个地方到达了一个空字符。

我猜在第一种情况下,char 数组是在堆上声明的,并且它被初始化为 0(但不是在所有地方,为什么?)并且 cout 的行为会根据 char 数组的长度而有所不同(为什么?)

我在这些示例中使用 Visual Studio 2010。

【问题讨论】:

  • 这是未定义的行为。你想多了。另外,我还没有听说过关于那本书的好消息。
  • 是的,我也不喜欢这本书,但 Stroustrup 版本似乎更像是对我的参考,我还没有真正听说过任何其他(好的)替代品。
  • 或许你可以看看SO's list

标签: c++ arrays string visual-studio-2010


【解决方案1】:

看起来您的 C++ 编译器正在以 4 字节块的形式分配空间,因此每个对象的地址都是 4 的倍数(转储中的十六进制地址可以被 4 整除)。编译器喜欢这样做,因为他们喜欢确保较大的数据类型,例如 intfloat(4 字节宽)与 4 字节边界对齐。编译器喜欢这样做,因为某些类型的计算机硬件需要更长的时间来加载/移动/存储未对齐的 intfloat 值。

在您的第一个示例中,每个数组需要 8 个字节的内存 - char 填充一个字节 - 因此编译器正好分配 8 个字节。在第二个示例中,每个数组是 3 个字节,因此编译器分配 4 个字节,用您的数据填充前 3 个字节,而第 4 个字节未使用。

现在在第二种情况下,未使用的字节似乎被一个空值填充,这解释了为什么cout 停止在字符串的末尾。但正如其他人指出的那样,您不能依赖未使用的字节来初始化任何特定值,因此无法保证程序的行为。

如果您将示例数组更改为 4 个字节,则程序的行为将与第一个示例相同。

【讨论】:

  • 是的,你是对的。在向 char 数组添加一个元素后,它的行为确实与第一个示例中一样。我仍然不确定为什么在第一种情况下内存(堆?!)被初始化为全 0,但在第二种情况下(堆栈?!)却没有。
  • 除了编写 VS 编译器的人之外,可能没有人不是。因为挖掘未定义的东西是浪费时间。
  • printf("%s",a); 的行为方式是否相同(逐字节遍历内存,直到到达空字符)?
【解决方案2】:

超出范围的内存内容是不确定的。访问您不拥有的内存,即使只是为了阅读,也会导致未定义的行为

【讨论】:

    【解决方案3】:

    这是一种未定义的行为,你不能说会发生什么。

    尝试使用其他系统可能会得到不同的输出。

    您的问题的答案是,它是一种未定义的行为,并且它的输出无法解释

    除了上述解释外,在您的特定情况下,您已全局声明数组。 因此,在您的第二个示例中,Peter Raynham\0 附加在四字节边界的第四个字节中作为explained

    【讨论】:

      【解决方案4】:

      '\0' 只是一个说明字符串有多长的解决方案。假设您通过在字符串之前存储一个值来知道它有多长。

      但是您的情况是,当您故意将其排除在函数之外时,通常您的代码也会继续搜索分隔符(这是一个空字符)。 指定内存的界限后面是什么是未定义的,它变化很大。 在带有gdb的调试模式下的Mingw中,它通常归零,没有gdb它只是垃圾......尽管这只是我的经验。 对于本地声明的变量,它们通常在堆栈上,因此您正在阅读的内容可能是您的调用堆栈。

      【讨论】:

        猜你喜欢
        • 2017-04-08
        • 1970-01-01
        • 2016-03-17
        • 2019-11-23
        • 1970-01-01
        • 1970-01-01
        • 2019-07-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多