为什么索引没有越界,尽管它在直觉上应该越界?

Why is the Index NOT out of bounds although it intuitively should?

我是 C 编程的新手,在 运行 以下代码并使用 gdb 和 lldb 对其进行调试时,我偶然发现了一个对我来说无法解释的行为。

简而言之:当交换索引 i 和 j (max i != max j) 在双嵌套 for 循环内访问二维数组中的值时,如果我访问使用 array[i][j] 或 array[j][i].

的值

这两个循环和数组大部分相同。

 unsigned matrix[3][1] =
   {
       {3},
       {4},
       {5}
   };

   //Loop1
   for (size_t i = 0; i < sizeof(matrix) / sizeof(*matrix); i++)
   {
        for (size_t j = 0; j < sizeof(matrix[i]) / sizeof(*matrix[i]); j++)
        {
            matrix[i][j] <<= 1;
            printf("matrix[%zu][%zu]  has the value: %d\n", i, j, matrix[i][j]);
        }
   }

   //same two dimensional array as matrix
   unsigned matrix2[3][1] =
   {
       {3},
       {4},
       {5}
   };

   //Loop2, basically the same loop as Loop1
   for (size_t i = 0; i < sizeof(matrix2) / sizeof(*matrix2); i++)
   {
        for (size_t j = 0; j < sizeof(matrix2[i]) / sizeof(*matrix2[i]); j++)
        {
            //swapped i and j here
            matrix2[j][i] <<= 1;
            printf("matrix2[%zu][%zu]  has the value: %d\n", j, i, matrix2[j][i]);
        }
   }

我是不是漏掉了什么?

在这两种情况下,i 都在外循环结束时传递值 2,而 j 在内循环结束时传递值 0。

直觉上,matrix[0][2] 应该抛出异常,因为每一行只有一个元素。

如果我的 C 技能不是 mega-rusty 你正在阅读“不安全的内存”。

基本上你的矩阵被声明为一个字节块。在该字节块之后还有更多字节。这些是什么?通常更多的变量被声明为程序的数据。一旦到达程序数据块的末尾,您就会到达用户代码内存块(编码的 ASM 指令)。

大多数语言会在您 运行 越界时执行检查并抛出异常,方法是通过某种方式跟踪最后一个有效访问的索引。 C 不会那样做,做这样的事情是你自己的责任。如果您不小心,您可能会覆盖程序代码的重要部分。


可以对不净化用户输入的 C 程序执行攻击,例如缓冲区溢出运行;它利用了它所描述的内容。

本质上,如果您声明一个长度为 N 的 char[] 并存储一个来自外部的字符串,而该字符串的长度恰好为 N+X,您将覆盖程序内存(指令)。

使用正确的字符序列,您可以将自己的汇编代码注入 运行ning 程序,该程序不会清理用户输入

糟糕,matrix[0][2] 应该抛出异常,因为每一行只有一个元素...

有些语言确实会在尝试越界访问时通过异常警告程序员,但 C 不会。它只是调用 Undefined Behaviour。从技术角度来看,这意味着编译器不必测试越界条件。从操作的角度来看,这意味着任何事情都可能发生,包括预期的行为......或立即崩溃......或修改不相关的变量......或......

我会采取与其他受访者略有不同的方法。

就内存布局而言,从技术上讲,您没有读取数组边界之外的内容。从人的角度来看你是(索引[0][2]不存在!),但是数组的内存布局是连续的。矩阵的每一“行”都存储在彼此旁边。

在内存中,您的数组存储为:| ? | 3 | 4 | 5 | ? | 因此,当您索引到 matrix[1][0]matrix [0][1] 时,您正在访问内存中的相同位置。如果您的数组宽度大于 1 维,则情况并非如此。

例如,将您的数组替换为以下数组并进行实验。您可以通过索引 matrix[0][2]matrix [1][0] 来访问整数“4”。位置 [0][2] 不应该 存在,但它确实存在,因为内存是连续的。

unsigned matrix[3][2] =
   {
       {3, 6},
       {4, 8},
       {5, 10}
   };

由于您的数组是 int 并且所有元素的大小都相同,所以我没有发现任何问题,因为您的数组存储在 RAM 中的连续 space 中,并且您使用矩阵的特殊情况,其中反转索引没有副作用。

  • 在第一个循环中,您的索引是 [0][0]、[1][0]、[2][0]
  • 在第二个循环中,您的索引是 [0][0]、[0][1]、[0][2]

现在尝试线性访问,因为您的数组作为线性数组保存到 RAM 中。

address of element = row * NCOL + col
row: is row number 
NCOL: number of columns into your matrix
col : the column number

so the linear index for :
[0][2] ==> 0 * 1 + 2 = 2 /* the third element*/ 
[2][0] ==> 2 * 1 + 0 = 2 /* always the third element */

但如果您使用 n x m 的矩阵,则 n >= 1 且 m > 1 且 n != m。 如果你反转索引,结果将不一样。

所以如果你采用 4 x 2 矩阵

linear index of [3][1] = 3 * 2 + 1 = 7
linear index of [1][3] = 1 * 2 + 3 = 5  /* even [1][3] is out of the range of your matrix index */
[1][3] you will manipulate the element [2][1]

所以在操作矩阵索引时要小心。