我对如何访问结构数组感到困惑。
简单案例:
typedef struct node
{
int number;
struct node *left;
struct node *right;
} node;
node *nodeArray = malloc(sizeof(node));
nodeArray->number = 5;
所以,这一切都是有道理的。但以下不起作用:
typedef struct node
{
int number;
struct node *left;
struct node *right;
} node;
node *nodeArray = malloc(511 * sizeof(node));
for(int i = 0; i < 511; i++)
{
nodeArray[i]->number = i;
}
然而,nodeArray[i].number=i似乎确实有效。有人能解释一下发生了什么,以及node *nodeArray = malloc(511 * sizeof(node));
和node (*nodeArray) = malloc(511 * sizeof(node));
之间的区别吗
在第一个片段中,以下内容都是等价的:
nodeArray->number = 5; // preferred
nodeArray[0].number = 5;
(*nodeArray).number = 5;
在第二个片段中,以下内容都是等效的:
(nodeArray + i)->number = i;
nodeArray[i].number = i; // preferred
(*(nodeArray + i)).number = i;
所以,正如你所看到的,有三种不同的语法可供选择,它们都做着相同的事情。当处理指向结构的单个实例的指针时,首选箭头语法(nodeArray->number
)。当处理指向结构数组的指针时,最好使用点表示法(nodeArray[i].number
)进行数组索引。明智的程序员避免使用第三种语法(取消引用指针和点表示法)。
当您分配这样的数组时
node* nodeArray = malloc(511*sizeof(node));
nodeArray
是一个指针,只需添加一个整数即可获得指向单个结构节点的指针:
CCD_ 6将给出指向第二节点的指针
nodeArray + 1
可以写成&nodeArray[1]
以便取消引用指针
*(nodeArray + 1).number
或写入nodeArray[1].number
问题可能是由对齐引起的:
您的节点结构包含一个整数和两个指针,其最小存储大小可以是12字节(在大多数32位架构上)或24字节(64位架构),但架构的对齐约束可能会迫使每个节点使用另一个最大存储大小(带有额外的填充,也需要分配)进行对齐。
sizeof(type)
只返回最小存储大小(即使在运行时或编译器未检查,也不应访问额外分配的填充)。
解决方案:使用calloc()
,它还将考虑阵列中每个项目的对齐约束!
替换:
node *nodeArray = malloc(511 * sizeof(node));
发件人:
node *nodeArray = calloc(511, sizeof(node));
现在您的代码通常是安全的,实际分配的大小将包括底层架构所需的必要的额外填充。
否则,您的代码是不可移植的。
请注意,一些C/C++编译器还提供了alignof(type)
来获得数据类型的正确对齐(它应该用于在C/C++库中实现void *calloc(size_t nitems, size_t size)
)。
上面的示例代码可能会出现缓冲区溢出,因为在循环中写入项之前没有为数组分配足够的空间。
当你使用简单类型时,你看不到区别(你不在乎它们的对齐或它们在哪里。它们是单独分配的,在堆栈上或使用它们的结构中可能会分配额外的填充,这是不可访问的,即使当它们的存储分配在物理寄存器内时不需要填充;但即使有"自动"或"寄存器"分配,编译器仍然可能在堆栈f上分配空间或者它,作为一个后备存储,当其他事情需要它时,或者在执行外部函数调用或C++中的方法调用之前,可以用来保存寄存器,并且函数体没有内联)。
请参阅C++11中alignof
和alignas
声明符的文档。关于它们有很多资源;例如:
https://en.cppreference.com/w/cpp/language/alignas
参见calloc()
的文档
(不要被Linux中使用的简化的32位或64位内存模型所混淆;即使是Linux现在也使用更精确的内存模型,考虑到对齐问题以及可访问性和性能问题,有时底层平台出于良好的安全原因会强制执行这些模型,以减少单个/统一的"扁平"内存模块中存在的攻击表面el:计算行业重新出现了分段体系结构,C/C++编译器必须进行调整:C++11对这个问题做出了回应,否则在编译的代码中需要成本更高或效率更低的解决方案,严重限制了一些优化,如缓存管理、TLB存储的效率、分页和虚拟内存,用户/进程/线程的强制安全作用域等等)。
请记住,每个数据类型都有自己的大小和对齐方式,并且它们是独立的。假设存在单个";尺寸";为数组中的数据类型进行分配是错误的(此外,在分配的数组末尾,在其最后一项之后,可能不会分配额外的填充,并且编译器或运行时可能会限制/强制对填充区域的读/写访问)。
现在还要考虑位字段(声明为具有额外精度/size参数的结构成员的数据类型)的情况:它们的sizeof()不是真正的最小值,因为它们可以被更紧密地封装(包括布尔值数组:一旦数据类型被提升为整数,sizeof(符号位的;通常编译器通过使用位掩码、移位或旋转来强制执行这些对填充位的无效访问;但是处理器可以提供更方便的指令来处理内存中字单元内甚至寄存器中的位,这样您的位字段就不会因为对其值进行算术运算而溢出和修改其他周围的位字段或填充位)。
此外,您的nodeArray[i]
返回节点对象的引用,而不是指针。因此nodeArray[i]->anything
无效:您需要用.
替换->
。