使用分配的空间来存储多个数组

时间:2016-07-19 19:47:31

标签: c arrays pointers memory-management c11

以下代码是否正确?

假设已知所有对象指针类型具有相同的大小和对齐,大小不超过8。

// allocate some space to A, and set *A and **A to different regions of that space
char*** A = malloc(92);
*A = (char**)( (char*)A + 2*sizeof(char**) );
**A = (char*)*A + 4*sizeof(char*);

// initialize the second char** object
A[1] = *A + 2;

// write four strings further out in the space
strcpy(A[0][0],"string-0-0");
A[0][1] = A[0][0] + strlen(A[0][0]) + 1;
strcpy(A[0][1],"string-0-1");
A[1][0] = A[0][1] + strlen(A[0][1]) + 1;
strcpy(A[1][0],"string-1-0");
A[1][1] = A[1][0] + strlen(A[1][0]) + 1;
strcpy(A[1][1],"string-1-1");

我发现这样的东西在如何释放对象可能不简单的情况下很有用。例如,假设A [1] [1]可能会或可能不会被重新分配给字符串文字的地址。无论哪种方式,你只需要释放A.此外,对malloc的调用次数也会最小化。

我担心这可能不是正确的代码基于以下内容。使用标准的草案版本,我有:

  

7.22.3内存管理功能

     
      
  1. ...如果分配成功,则返回的指针被适当地对齐,以便可以将其指定给具有基本对齐要求的任何类型对象的指针,然后用于访问此类对象或此类对象的数组。分配的空间(直到空间被明确解除分配)......
  2.   

所以我保证能够将空间用作单一类型的数组。我找不到任何保证,我可以将它用作两个不同类型(char *和char **)的数组。请注意,将某些空间用作char数组是唯一的,因为任何对象都可以作为字符类型数组进行访问。

有效类型的规则与此方法一致,因为没有单独的字节被用作两种不同类型的一部分。

虽然上述情况表明似乎没有明确违反标准,但标准也没有明确允许这种行为,我们有第4章第2段(强调增加):

  

如果违反约束或运行时约束之外的''shall''或''shall not''要求,则行为未定义。未定义的行为在本国际标准中以“未定义的行为”或省略任何明确的行为定义的方式表示。这三者之间的重点没有区别;他们都描述了“未定义的行为”。

当记忆模型本身如此含糊时,这有点模糊。使用malloc()返回的空间来存储任何(一个)类型的数组显然需要明确的容差,我在上面引用了这个容差。因此有人可能会争辩说,将该空间用于不同类型的不相交数组也需要明确的限制,如果没有它,则按照第4章保留为未定义的行为。

所以,具体来说,如果代码示例是正确的,那么根据上面引用的标准第4章的部分,未明确定义并因此未定义的参数有什么问题?

1 个答案:

答案 0 :(得分:1)

如果任何对象从分配区域的起点偏移是对齐的倍数,并且假设在分配的生命周期内没有任何内存被用作多个类型,则不会问题

一个令人讨厌的问题是,虽然有一些算法(例如用于散列表),但是对于最初填充了任意值的表(给定可能正确或可能不正确的值),代码可能能够很好地工作为了更快地确定值是否正确--O(1)vs O(N) - 而不是在没有初始猜测的情况下找到正确的值),这种行为在使用gcc或clang时可能不可靠。他们解释标准的方式,将内存写为一种类型并将其作为另一种非字符类型读取会产生未定义的行为,即使目标类型没有陷阱表示,即使转换的指针转换为新类型也不会被使用(在此之后,即使代码在任何值上都能正常工作,在数据尚未写入新类型的情况下,读取可能会产生。

假设:

float *fp;
uint32_t *ip;

*fp = 1.0f;
*ip = 23;

如果fp和ip识别相同的存储,则会定义行为 之后需要存放23个存储空间。另一方面,给出:

float *fp;
uint32_t *ip;

*fp = 1.0f;
uint32_t temp = *ip;
*ip = 23;

编译器可以利用Undefined Behavior重新排序 操作所以写入* fp后写入* ip。如果数据被重用于哈希表之类的东西,那么编译器就不太可能有效地应用这种优化,但是“智能”编译器可能会重新排序无用数据的写入超过有用数据的写入。这种“优化”不太可能破坏事物,但除了通过释放和重新分配存储(如果使用托管系统并且可以容忍性能损失和可能的碎片)之外,没有标准定义的方法来阻止它们。或者在重新使用之前清除存储(可以将O(1)操作转换为O(N))。