无法解释以下 C++ 代码段的输出

Posted

技术标签:

【中文标题】无法解释以下 C++ 代码段的输出【英文标题】:Unable to explain the output for the following C++ snippet 【发布时间】:2018-07-12 09:21:21 【问题描述】:
#include <bits/stdc++.h>
using namespace std;

int main() 
   int dp[5][6];
   memset(dp,0,sizeof(dp));
   dp[1][0]=0;
   for(int i=1;i<=9;i++)
      dp[1][i]=1;
   
   cout<<dp[2][0]<<endl;
   cout<<dp[3][0]<<endl;
 

上面的 sn-p 产生了一个意外的输出:

1
0

但是,当我将 dp 数组大小更改为 dp[100][100] 时, 我得到预期的输出:

0
0

我也尝试打印 2D 矩阵大小:

sizeof(dp)/sizeof(int)

我得到了正确的值:dp[5][6] 为 30,dp[100][100] 为 10000。 我使用 Linux 中的标准 g++ 编译器编译并执行了代码。 有人可以向我解释这个错误输出的原因吗?

【问题讨论】:

尝试打印 sizeof(dp) ,我感觉它的实际值与你期望的不同。 您的 for 循环编辑数组边界之外的元素。这是未定义的行为。它很可能开始将[2][0] 覆盖为[2][3],因为数组在内存中是连续的,但您永远无法确定。 我想你有来自dp[1][6]=1;的UB。越界访问。 sizeof(dp) 给出的预期答案为 120 (4*5*6)。 @Yksisarvinen 这是真的。我正在大力这样做。我无法解释它如何更改范围内且未超出范围的元素?编译器是否将索引包装在 2D 矩阵中? 【参考方案1】:

数组的维度是[5][6]。因此,当您尝试使用从19i 执行dp[1][i] = 1 时,在某些迭代中,dp[1][6] = 1 完成。由于 'row' 中的元素数量为 6,它执行与 dp[2][0] = 1 相同的操作,因为该二维数组的所有元素都按顺序存储。

所以基本上当您尝试访问dp[1][i] 时,它就像startOffset + 1 * rowSize + i。并且i 大于行大小。

【讨论】:

@FantasticMrFox 不,这不是 UB。数组的元素需要按顺序存储。想象一下这个数组将被声明为typedef int row_t[6]; row_t dp[5];ints里面的每个row_t对象都是按顺序存储的,每个row_t对象按顺序存储在dp-array中。 sizeof(row_t)6 * sizeof(int)sizeof(dp)5 * sizeof(row_t)。这些元素不可能不按顺序存储。【参考方案2】:

附带说明,在 C++ 中使用 memset 对数组进行零初始化是一种不好的做法。最佳做法是使用aggregate initializer syntax:

int dp[5][6] = ; // Zero-initialize.

【讨论】:

【参考方案3】:

首先,这不是很好的代码风格,但是由于我不确定您要达到的目标,因此我不会建议如何改进它,而只是回答您的问题:解释输出。

第一种情况

int main() 
   int dp[5][6];
   memset(dp,0,sizeof(dp));
   dp[1][0]=0;
   for(int i=1;i<=9;i++)
      dp[1][i]=1;
   
   cout<<dp[2][0]<<endl;
   cout<<dp[3][0]<<endl;
 

dp 的第二个索引范围从 05(因为您声明了 int dp[5][6] 并且 C++ 数组是从零开始的)。但是,然后将第二个索引(for 循环中的i)从1 循环到9。因此,索引6, 7, 8, 9 超出范围,通常这会使您的程序崩溃。

它不会崩溃,因为您已经声明了一个二维数组,该数组被分配在一个连续的内存块中,因此dp[1][6] 溢出到二维数组的下一部分。即dp[1][6] 等价于dp[2][0]

这就是为什么在第一种情况下,您会发现 dp[2][0] 等于 1

但是,当您将声明更改为 dp[100][100] 时,6, 7, ... 是有效的第二个索引,因此它永远不会溢出,dp[2][0] 保持其初始值 0

【讨论】:

感谢您的反馈。当我收到这个错误时,我正在为一个问题编写一个 dp 解决方案。我的想法是越界访问元素会使程序崩溃。 @pratyushchaudhary 从技术上讲,访问具有越界索引的普通数组会导致未定义行为。也就是说,该标准没有说明编译器/运行时应该做什么。 通常你会发现它只是崩溃,但不要依赖它。如果您想要“更友好”的错误处理,请使用像 std::array&lt;T&gt;std::vector&lt;T&gt; 这样的 STL 容器。这些都更加更安全,轻微的性能损失几乎总是值得的。【参考方案4】:

首先要提到的是 dp[5][6] 是一个二维数组,其中有 5 行,每行有 6 列。您可以将其可视化为网格。但实际情况是每一行都是依次排列的。第一行可以容纳 0 到 5。现在,当您在循环中分配 dp[1][6]=1 时,这意味着 dp[2][0] 被分配 1,因为 dp[1][5] 是第一行的第 1 行和第 6 个元素是第 2 行的第一个元素。对于 dp[2][1]、dp[2][2] 和 dp[2][3],您将得到相同的答案。但会在 dp[2][4] 处得到 0。

有一件事要提一下,它在不同的架构中可能会有所不同。同样,不同的编译器可以以不同的方式工作。

【讨论】:

以上是关于无法解释以下 C++ 代码段的输出的主要内容,如果未能解决你的问题,请参考以下文章

c++ 中的预处理器指令:以下代码的输出是啥?

确定代码段的时间复杂度

已安装 Rcpp,但来自复杂代码段的编译错误

Python中 设计一个程序,输出你的中文姓名和姓名中每个字的unicode编码。(要求

C++调用子函数

获取STM32代码运行时间的技巧