指向C风格字符串的无效指针传递了数值

4
我是一名有用的助手,可以为您进行文本翻译。以下是需要翻译的内容:

我目前正在学习《C++ Primer》。在其中一个练习问题中,它问道:

下面的程序是做什么用的?

const char ca[] = { 'h', 'e', 'l', 'l', 'o' };

const char *cp = ca;

while (*cp)
{
    cout << *cp << endl;

    cp++;
}

我很高兴地理解 *cp 将继续保持为 true,直到 ca[] 数组的最后一个字符,因为该数组中没有空字符作为最后一个项目。这更多是出于我的好奇心,想知道 while 循环是什么使其变为 false。在我的计算机上似乎总是显示 19 个字符。0-4 是 hello 字符串,5-11 总是相同的,12-19 在每次执行时都会改变。
#include <iostream>

using namespace std;

int main( )
{
    const char ca[ ] = { 'h', 'e', 'l', 'l', 'o'/*, '\0'*/ };

    const char *cp = ca;

    int count = 0;

    while ( *cp )
    {
        // {counter} {object-pointed-to} {number-equivalent}
        cout << count << "\t" << *cp << "\t" << (int)*cp << endl;

        count++;
        cp++;
    }

    return 0;
}

问题:是什么导致 while 循环无效?为什么 5-11 总是相同的字符?
4个回答

3

C++允许你通过指向ca[4]的下一个位置来创建指向ca[0]的指针。但是,你只能对指向ca[0]ca[4]的指针进行解引用操作(即应用*操作符);指向ca[4]的指针不可用。一旦你对指针进行解引用操作,其行为将是未定义的,程序可能产生任何数据,甚至崩溃。

实际上发生的情况更简单:指针只是内存中的地址,因此对它进行解引用操作会继续向程序传递数字。在某个时刻,该地址包含一个零字节。这是你的程序停止的时候。

数组是在自动内存中分配的。大多数编译器使用CPU堆栈。字节5..20的内容很可能包括cpcount,因为编译器倾向于将本地变量放在一起。其中可能还有一些填充,因为指针和int通常对齐在可以被4整除的地址上。当然,你不能指望任何这些发生,因为其他编译器会以不同的方式处理。


2

由于访问超出数组边界的语言是未定义的,如果您想了解发生了什么,您需要了解您的“平台”如何工作。

对于大多数编译器,您的内存可能布局如下:

|H|e|l|l|o|XXX|____cp___|__count__|

XXX是指为了对齐到8而必须添加的“填充字节”。编译器通常会在调试版本中用固定值来填充这些字节,以避免停止(这样您就可以发现它)。

cp是一个指向每次递增的“H”的指针。它的值通常是进程堆栈在进程本身中的地址映射。

此地址通常具有固定前缀和随着嵌套调用深入而增长的偏移值。

由于指针(可能)长度为8个字节(由于x86处理器的低位优先,最后四个字节放置在第一个字节之前),所以您得到的是一个迭代,其中包括:

  • 五个“Hello”字符
  • 三个填充字符(假设它们可打印)
  • 相对于堆栈开头的cp偏移量(始终相同,因为main函数始终在相同位置)
  • 进程前缀的一部分(每次调用时都会更改)

此前缀可能在某个点上包含一个“0”,从而终止循环。

请注意,尽管这种解释有道理,但您不能以任何方式信任它用于编译为不同平台的生产代码,甚至可能由不同的编译器编译,因为它们管理变量的方式也可能不同。


感谢大家的回复。我知道在数组中超过最后一个元素会导致未定义的行为。在我的想象中,我期望它会一直持续到找到一个“0”。我对“Hello”之后出现的一致性感到惊讶。这都是学习新语言的乐趣,特别是从C#转换过来。感谢Emilio(和其他人)提供的有用回复。Matt。 - Compton

1
重要的是要知道,您正在经历未定义行为。因此,无论您现在看到什么,在使用不同的编译器或不同的编译器选项时可能会有所不同。
“常量”值在5-11上最可解释的原因是您正在读取堆栈的一部分,这恰好每次都具有相同的值。

0
首先:
这个:存储了一个字符数组 (可修改的字符串),包括 \0 空终止符:
const char ca[] = "hello";

这不包括空终止符。它从您的初始化列表初始化数组。

const char ca[] = { 'h', 'e', 'l', 'l', 'o' };

它的工作方式与你所做的相同:

const int ib[] = {2, 5, 7, 9};

这里是有道理的,因为编译器不应该向您的数组添加额外的内容。


const char ca[] = { 'h', 'e', 'l', 'l', 'o' };
const char *cp = ca;
while (*cp){
    cout << *cp << endl;
    cp++;
}

你的代码中存在未定义行为,因为在数组中没有\0空终止符,所以你将会超出数组范围。

是什么导致while循环无效?

当打印完数组中的最后一个字符后,程序会继续从一个未定义(未知)的内存位置读取并打印,直到找到0并退出循环。

为什么5-11始终是相同的字符?

至于它们为什么看起来相同:栈变量按照编译器的意愿进行排序;内存也有填充;同样,内存被重用,因此你从stuff的地址中读取,这是你无法合法操作的。


注:在int main()之前,您的程序可能会调用其他函数。(它调用了什么并不关您的事)。这些函数初始化静态变量,其中包括像std::cout这样的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接