据我所知,堆栈上的多维数组将按行顺序占用连续内存。根据ISO C ++标准使用指向元素的指针索引多维数组是不确定的行为?例如:
#include <iostream>
#include <type_traits>
int main() {
int a[5][4]{{1,2,3,4},{},{5,6,7,8}};
constexpr auto sz = sizeof(a) / sizeof(std::remove_all_extents<decltype(a)>::type);
int *p = &a[0][0];
int i = p[11]; // <-- here
p[19] = 20; // <-- here
for (int k = 0; k < sz; ++k)
std::cout << p[k] << ' '; // <-- and here
return 0;
}
如果指针没有超出数组a
的边界,则上面的代码将编译并正确运行。但这是否因为编译器定义的行为或语言标准而发生?任何来自ISO C ++标准的参考都是最好的。
答案 0 :(得分:9)
我相信你的例子中的行为在技术上是未定义的。
标准没有多维数组的概念。您实际声明的是一个由4个整数和#34;组成的5个数组的数组。那是a[0]
和a[1]
实际上是两个不同的4个整数数组,它们都包含在数组a
中。这意味着a[0][0]
和a[1][0]
不是同一数组的元素。
[expr.add] / 4说以下(强调我的)
当向指针添加或从指针中减去具有整数类型的表达式时,结果具有类型 指针操作数。如果指针操作数指向数组对象的元素,则数组为 足够大,结果指向一个元素偏离原始元素,使得差异 结果和原始数组元素的下标等于整数表达式。换句话说,如果 表达式P指向数组对象的第i个元素,表达式(P)+ N(等效地,N +(P)) 和(P)-N(其中N具有值n)分别指向阵列的第i + n和第i-n个元素 对象,只要它们存在。而且,如果表达式P指向数组对象的最后一个元素, 表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向 一个超过数组对象的最后一个元素,表达式(Q)-1指向数组的最后一个元素 宾语。 如果指针操作数和结果都指向同一个数组对象的元素,或者一个过去 数组对象的最后一个元素,评估不应产生溢出;否则,行为是 未定义强>
因此,由于p[11]
扩展为*(p + 11)
,因为p
和p + 11
不是同一数组的元素(一个是a[0]
的元素,另一个是超过a[0]
末尾的一个元素,该添加的行为未定义。
答案 1 :(得分:8)
这里的问题是严格别名规则,它存在于我的草案n3337 for C ++ 11 3.10 Lvalues和rvalues [basic.lval]§10中。这是一个详尽无遗的列表探索允许将多维数组别名为一维整数。
因此,即使确实需要在内存中连续分配数组,这证明多维数组的大小(例如T arr[n][m]
)是维度乘以元素大小的乘积:{ {1}}。转换为char指针时,您甚至可以对整个数组执行算术指针操作,因为任何指向对象的指针都可以转换为char指针,并且该char指针可用于访问对象的连续字节(*)
但遗憾的是,对于任何其他类型,标准只允许在一个数组中进行算术指针操作(并且按照定义取消引用数组元素与取消引用指针相同指针算术后:n * m *sizeof(T)
a[i]
)。因此,如果你们都尊重指针算术规则和严格别名规则,那么多维数组的全局索引不是由C ++ 11标准定义的,除非你通过char指针算术:
*(a + i)
char指针算术以及打破大量现有代码的恐惧解释了为什么所有知名编译器都默默地接受具有相同全局大小的1D多维数组的别名(它导致相同的内部代码),但从技术上讲,全局指针算术只对char指针有效。
(*)标准声明在1.7 C ++内存模型[intro.memory]中
C ++内存模型中的基本存储单元是字节... C ++程序可用的内存由一个或多个连续字节序列组成。一切 byte有一个唯一的地址。
以及后来的3.9类型[basic.types]§2
对于普通可复制类型T的任何对象(基类子对象除外),是否对象 保存类型为T的有效值,组成对象的基础字节可以复制到数组中 char或unsigned char。
要复制它们,您必须通过int a[3][4];
int *p = &a[0][0]; // perfectly defined
int b = p[3]; // ok you are in same row which means in same array
b = p[5]; // OUPS: you dereference past the declared array that builds first row
char *cq = (((char *) p) + 5 * sizeof(int)); // ok: char pointer arithmetics inside an object
int *q = (int *) cq; // ok because what lies there is an int object
b = *q; // almost the same as p[5] but behaviour is defined
或char *
答案 2 :(得分:0)
如果您声明
int arr[3][4][5];
arr
的类型为int[3][4][5]
,arr[3]
的类型为int[4][5]
等。数组数组的数组,但不是指针数组。让我们看看如果我们增加第一个索引会发生什么?它会按照数组元素的大小向前移动指针,但是arr的数组元素是一个二维数组!它相当于递增:arr + sizeof(int[4][5])/sizeof(int)
或arr + 20.
以这种方式迭代我们会发现arr[a][b][c]
等于*(*(*(arr + a) + b) + c)
,前提是数组中永远不会有任何填充(以符合POD类型与C99的强制兼容性):
*((int*)arr + 20*a + 5*b + c)
添加或减去具有整数类型的表达式时 从指针开始,结果具有指针操作数的类型。如果 指针操作数指向数组对象的元素和数组 足够大,结果指向一个偏离的元素 原始元素使得下标的差异 结果和原始数组元素等于整数表达式