【问题标题】:Using an allocated space to store multiple arrays使用分配的空间来存储多个数组
【发布时间】:2016-07-19 19:47:30
【问题描述】:

以下代码是否正确?

假设已知所有对象指针类型具有相同的大小和对齐方式,大小不大于 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. ...如果分配成功,则返回的指针经过适当对齐,以便可以将其分配给指向具有基本对齐要求的任何类型对象的指针,然后用于访问此类对象或此类对象的数组分配的空间(直到空间被显式释放)...

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

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

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

如果违反了出现在约束或运行时约束之外的“应”或“不应”要求,则行为未定义。未定义的行为在本国际标准中以“未定义的行为”一词或省略任何明确的行为定义来表示。这三者在重点上没有区别;它们都描述了“未定义的行为”。

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

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

【问题讨论】:

  • char*** A = malloc(90); 错误,指针大小为 8
  • @4386427,谢谢,我的算术有点草率。
  • @4386427,我得到 6 个大小为 8 的指针,大小为 6*8=48,长度为 10 的 4 个字符串分别为 44、48+44=92。
  • 你知道C有structs吗?
  • malloc(92) 让我眼前一亮。永远不要那样做。真的。用于初始化非char 指针的malloc 应始终将sizeof 表达式作为参数。

标签: c arrays pointers memory-management c11


【解决方案1】:

假设任何对象从分配区域开始的偏移量是对齐的倍数,并且假设在分配的生命周期内没有一块内存被用作多于一种类型,则不会有问题。

一个令人讨厌的问题是,虽然有一些算法(例如哈希表)可以很好地处理最初填充任意值的表(给定一个可能正确或可能不正确的值,代码可能能够为了更快地确定值是否正确(O(1) 与 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;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-19
    • 1970-01-01
    • 2015-05-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多