据我所知,堆栈上的多维数组将按行顺序占用连续内存.根据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;
}
Run Code Online (Sandbox Code Playgroud)
如果指针没有超出数组的边界,上面的代码将编译并正确运行a.但这是否因为编译器定义的行为或语言标准而发生?ISO C++标准中的任何参考都是最好的.
我相信你的例子中的行为在技术上是未定义的.
该标准没有多维数组的概念.您实际声明的是"5个4个数组的数组".那就是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]),因此该添加的行为是未定义的.
但是,我会非常惊讶地发现任何实现,其中这样的添加导致除了您期望的之外的任何其他内容.
这里的问题是我的草案n3337中存在的严格别名规则,适用于3.10 Lvalues和rvalues中的C++ 11和rvalues [basic.lval]§10.这是一个详尽的列表,它不会轻易地允许将多维数组别名为一维一个整体大小.
因此,即使确实需要在内存中连续分配数组,这证明了多维数组的大小,例如T arr[n][m],维度是元素大小的乘积:n * m *sizeof(T).转换为char指针时,您甚至可以对整个数组执行算术指针操作,因为任何指向对象的指针都可以转换为char指针,并且该char指针可用于访问对象的连续字节(*) .
但遗憾的是,对于任何其他类型,标准只允许在一个数组中进行算术指针操作(并且根据定义,取消引用数组元素与在指针算术之后取消引用指针相同:a[i] 是 *(a + i)).因此,如果您既尊重指针算术规则和严格别名规则,那么多维数组的全局索引不是由C++ 11标准定义的,除非您通过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
Run Code Online (Sandbox Code Playgroud)
这个char指针算术以及打破许多现有代码的恐惧解释了为什么所有众所周知的编译器默默地接受具有相同全局大小的1D多维数组的别名(它导致相同的内部代码),但在技术上,全局指针算术仅对char指针有效.
(*)该标准在1.7 C++内存模型[intro.memory]中声明
C++内存模型中的基本存储单元是字节... C++程序可用的内存由一个或多个连续字节序列组成.每个字节都有一个唯一的地址.
后来在3.9类型[basic.types]§2
对于普通可复制类型T的任何对象(基类子对象除外),无论对象是否保持类型T的有效值,组成对象的基础字节都可以复制到char或unsigned char数组中.
要复制它们,您必须通过char *或访问它们unsigned char *
| 归档时间: |
|
| 查看次数: |
1059 次 |
| 最近记录: |