为什么索引没有越界,尽管它在直觉上应该越界?
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]
所以在操作矩阵索引时要小心。
我是 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]
所以在操作矩阵索引时要小心。