【问题标题】:What's C++ Really Doing When I Accidently Use a Variables to Declare Array Length?当我不小心使用变量来声明数组长度时,C++ 到底在做什么?
【发布时间】:2009-12-03 07:44:47
【问题描述】:

我正在帮助一位朋友完成一些 C++ 作业。我警告说朋友,我做的那种编程(PHP、Perl、Python)与 C++ 完全不同,而且我不能保证我不会说可怕的谎言。

我能够回答他的问题,但并非没有绊倒我自己的动态背景。当我重新熟悉 C++ 数组语义时,我做了一些像这样愚蠢的事情(简化示例以使我的问题更清楚)

 #include <iostream>
 #include <cstring>
 using namespace std;
 int main()
 {
   char easy_as_one_two_three[] = {'A','B','C'};  
   int an_int = 1;

   //I want an array that has a length of the value 
   //that's currently in an_int (1)
   //This clearly (to a c++ programmer) doesn't do that.
   //but what is it doing?
   char breaking_things[an_int];

   cout << easy_as_one_two_three << endl;
   return 1;
 }

当我编译并运行这个程序时,它会产生以下输出

 ABC????

但是,如果我注释掉我的虚假数组声明

 #include <iostream>
 #include <cstring>
 using namespace std;
 int main()
 {
   char easy_as_one_two_three[] = {'A','B','C'};  
   int an_int = 1;

   //I want an array that has a length of the value 
   //that's currently in an_int (1)
   //This clearly (to a c programmer) doesn't do that.
   //but what is it doing?
   //char breaking_things[an_int];

   cout << easy_as_one_two_three << endl;
   return 1;
 }

我得到了我期望的输出:

 ABC

那么,这里到底发生了什么?我(模糊地)理解,当你创建一个数组时,你指向一个特定的内存地址,当你给数组一个长度时,你是在告诉计算机“为我保留下一个 X 块”。

我不明白的是,当我在数组声明中使用变量时,我在告诉计算机做什么,为什么会对完全独立的数组产生影响?

编译器是g++,版本字符串是

 science:c++ alanstorm$ g++ -v
 Using built-in specs.
 Target: i686-apple-darwin9
 Configured with: /var/tmp/gcc/gcc-5493~1/src/configure --disable-checking -enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ --with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib --build=i686-apple-darwin9 --with-arch=apple --with-tune=generic --host=i686-apple-darwin9 --target=i686-apple-darwin9
 Thread model: posix
 gcc version 4.0.1 (Apple Inc. build 5493)

【问题讨论】:

  • 您的输出与 an_int 或使用变量指定长度的数组声明无关。
  • @Murali 我更新了我的帖子来解释为什么我认为虚假输出与数组声明有关。
  • @Alan 这很有趣。
  • 即使您将虚假部分注释掉,它也不总是打印 ABC - 请参阅我的更新。
  • 如果使用 g++,您应该始终使用 -Wall 和 -pedantic 标志编译代码。

标签: c++ arrays pointers


【解决方案1】:

更新: Neil 在他对这个问题的评论中指出,如果你在 g++ 中使用 -Wall-pedantic 标志编译它会出错。

error: ISO C++ forbids variable-size array

您得到ABC????,因为它打印数组的内容 (ABC) 并继续打印,直到遇到 \0

如果数组是{'A','B','C', '\0'};,输出将只是ABC,正如预期的那样。

Variable-length arrays 是在 C99 中引入的 - 不过这似乎不适用于 C++。


这是未定义的行为。即使您注释掉虚假声明,打印的输出也不总是您所期望的 (ABC)。尝试将一些可打印字符(介于 32 和 126 之间)的 ASCII 值提供给 an_int 而不是 1,您会看到差异。

an_int            output
------------------------
 40                ABC(
 65                ABCA
 66                ABCB
 67                ABCC
 296               ABC(
 552               ABC(
 1064              ABC(
 1024*1024 + 40    ABC(

看到这里的模式了吗?显然它将an_int 的最后一个字节(LSB)解释为一个字符,打印它,然后以某种方式找到一个空字符并停止打印。我认为“不知何故”必须与 an_int 的 MSB 部分用零填充,但我不确定(也无法得到任何结果来支持这个论点)。

更新:它是关于被填充零的 MSB。我得到了以下结果。

ABC( 用于 40 -(3 个零字节和一个 40),
ABC(( 用于 10280(即 (40 ABC((( 表示 2631720(即 (10280 ABC((((°¿® 表示 673720360(即 (2631720 ABCDCBA0á´¿á´¿® for (((((65

这些结果是在具有 8 位原子元素大小和 1 字节地址增量的 little endian processor 上获得的,其中 32 位整数 40(十六进制中的 0x28)表示为 0x28-0x00-0x00-0x00(最低地址处的 LSB)。结果可能因编译器和平台而异。

现在,如果您尝试取消对虚假声明的注释,您会发现所有输出的格式均为 ABC-randomchars-char_corresponding_to_an_int。这又是未定义行为的结果。

【讨论】:

  • Amarghosh,请参阅我更新的帖子。当我省略“char Breaking_things[an_int];”时我得到了我期望的输出结果。为什么带有注释行的那个版本的程序在没有 null 的情况下停止,而以前的版本没有?
  • 您的程序产生了未定义的行为。您的“为什么”问题没有真正的答案。无论出于何种方式和目的,您的程序的行为都可以被视为随机
  • 我对其进行了测试并重现了该行为 - 但不知道为什么会发生这种情况。将修改帖子以反映变量数组不适用于 C++
  • @AndreyT,除了你对 dick 引号的使用,我当然相信像这样的无效语法会导致(基本上)随机行为。我不明白的是为什么 c++ 编译器允许这种无效的语法通过。
  • 我的猜测是,数组之后的堆栈内存以某种方式分配了 0,当您分配第二个数组时,它被放置在内存中的第一个数组旁边,使 cout 继续找到它找到的下一个 0。正如已经说过的,这是未定义的行为,但这并不意味着它是完全随机的。
【解决方案2】:

这不会让你“重新认识”你“使用 c++ 数组语义”,因为在 C++ 中它完全是非法的。在 C++ 中,数组只能使用由 Integral Constant Expressions (ICE) 定义的大小来声明。在您的示例中,大小不是 ICE。它只是因为 GCC 特定的扩展而编译。

从 C 的角度来看,这在 C99 版本的语言中实际上是完全合法的。它确实产生了一个所谓的长度为 1 的可变长度数组。所以你的“显然”评论是不正确的。

【讨论】:

  • 我不太确定我是否遵循了这一点。你说这是非法的 C++ 语法(我相信)。如果是这样的话,为什么 g++ 编译程序而不是因为我做错了什么而大喊大叫?您提到了“特定于 GCC 的扩展”?那是什么扩展? GCC 是否认为语法没问题,因为在 C 编程语言(无论如何是 C99)中是这样?
  • 可变长度数组是 C99 特性。它们不能在 C++ 中工作,除了 gcc,它通过允许它们违反了标准。 (是的,我想这有点令人困惑。)
  • @Alan:GCC 甚至在 C99 之前就支持具有运行时大小的数组对象。它们被支持为 C++ 和 C 语言扩展。甚至可以说 VLA 的想法(以及该语言的其他一些补充)在很大程度上来自 GCC 的 C99。如果您尝试在 -ansi -pedantic -Wall 模式下使用 g++ 编译代码,编译器应该会告诉您您依赖于非标准的编译器特定扩展。
【解决方案3】:

这不是无效的语法。它在语法上很好。

它在语义上是无效的 C++,并被我的编译器 (VC++) 拒绝。 g++ 似乎有一个扩展,允许在 C++ 中使用 C99 VLA。

问号的原因是您的三个字符数组不是以空结尾的;它一直在打印,直到在堆栈上找到一个空值。堆栈的布局受堆栈上声明的变量的影响。使用数组,布局是这样的,在第一个 null 之前有垃圾;没有数组就没有。就是这样。

【讨论】:

    【解决方案4】:

    你得到你期望或不期望的输出是靠运气。因为您没有终止数组中的字符,所以当您将其打印出来以 cout 时,它将打印 A、B 和 C,以及它找到的任何其他内容,直到它遇到 NULL 字符。使用数组声明,编译器可能会将某些东西压入堆栈以在运行时调整数组的大小,这会在 A、B 和 C 之后留下垃圾字符,而当你不存在时恰好是 0在堆栈上的 C 之后。

    再一次,这只是愚蠢的运气。要始终获得您期望的结果,您应该这样做:char easy_as_one_two_three[] = { 'A','B','C','\0'}; 或者,可能更有用的是 char easy_as_one_two_three[] = "ABC";,它将正确地终止字符串。

    【讨论】:

      【解决方案5】:

      char broken_things[an_int] 正在分配大小为 an_int 的 char 数组(在您的情况下为 1),它被称为 variable length array,它是一个相对较新的功能。

      在这种情况下,更常见的是使用 new 动态分配内存:

      char* breaking_things = new char[an_int]; // C++ way, C programmer would use malloc
      

      【讨论】:

      • 它是C语言的一个“比较新的特性”。 C ++中没有VLA之类的东西。问题是关于 C++ 的。
      • 你是对的,但是一些 C++ 编译器支持它,他曾经提到过 C 程序员。
      【解决方案6】:

      破坏事物的可能不是break_things。第一个数组不是 NUL (\0) 终止的字符串,这解释了输出 - cout 将打印 ABC 之后的任何内容,直到它遇到的第一个 NUL。

      至于break_things 的大小,我怀疑编译器之间会有所不同。我相信至少早期版本的 gcc 使用了变量在编译时碰巧具有的任何值,这可能很难确定。

      【讨论】:

      • 查看我更新的问题。当我删除(显然在 c++ 中无效但不知何故通过编译器)有问题的数组声明时,我得到了我期望的输出。
      【解决方案7】:

      输出是这样的,因为它会打印 char 数组的内容,直到找到一个空字符。

      确保 char 数组必须是以空字符结尾的字符串并指定数组的大小 --> 总字符数 + 1(对于空字符)。

      【讨论】:

      • 查看我更新的问题。当我删除(显然在 c++ 中无效但不知何故通过编译器)有问题的数组声明时,我得到了我期望的输出。
      • 仅仅因为你得到了你期望的输出并不意味着你的代码是正确的。有时,您会正确地猜到“未定义的行为”会做什么,要么是因为您对编译器的内部工作有所了解,要么是纯粹的运气。这并不正确。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-01-08
      • 2013-08-02
      • 2021-09-18
      • 2012-12-17
      • 2023-03-24
      • 1970-01-01
      • 2015-08-15
      相关资源
      最近更新 更多