使用指向one-past-malloc的指针是否定义明确?

时间:2017-12-20 07:03:28

标签: c malloc language-lawyer bounds dynamic-allocation

在C中,只要你不取消引用它,最好制作一个指向数组最后一个元素的指针,并在指针算术中使用它:

int a[5], *p = a+5, diff = p-a; // Well-defined

然而,这些是UB:

p = a+6;
int b = *(a+5), diff = p-a; // Dereferencing and pointer arithmetic

现在我有一个问题:这是否适用于动态分配的内存?假设我只是在指针算术中使用一个指向最后一个的指针,而没有取消引用它,并且malloc()成功。

int *a = malloc(5 * sizeof(*a));
assert(a != NULL, "Memory allocation failed");
// Question:
int *p = a+5;
int diff = p-a; // Use in pointer arithmetic?

4 个答案:

答案 0 :(得分:26)

C11的n4296草案明确指出一个完全定义一个数组:6.5.6语言/表达式/加法运算符:

  

§8当一个具有整数类型的表达式被添加到指针或从指针中减去时,   result具有指针操作数的类型。 ...此外,如果表达式P指向最后一个   数组对象的元素,表达式(P)+1指向一个过去的最后一个元素   数组对象,如果表达式Q指向一个数组对象的最后一个元素,   表达式(Q)-1指向数组对象的最后一个元素...如果结果指向一个超过数组对象的最后一个元素,则   不得用作被评估的一元*运算符的操作数。

由于子句中的内存类型从未预先准备好,因此它适用于任何类型的内存,包括分配的内存。

这显然意味着:

int *a = malloc(5 * sizeof(*a));
assert(a != NULL, "Memory allocation failed");

两者

int *p = a+5;
int diff = p-a;

是完美定义的,并且通常的指针算术规则适用,diff将收到值5

答案 1 :(得分:22)

  

使用指向one-past-malloc的指针是否明确定义?

如果p指向已超出分配的内存并且未解除引用,则定义良好。

n1570 - §6.5.6(p8):

  

[...]如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数。

减去两个指针只有当它们指向同一个数组对象的元素或者超过数组对象的最后一个元素时才有效,否则会导致未定义的行为。

(p9)

  

当减去两个指针时,两个指针都指向同一个数组对象的元素,或者指向数组对象的最后一个元素[...]

以上引用适用于动态和静态分配的内存。

int a[5];
ptrdiff_t diff = &a[5] - &a[0]; // Well-defined

int *d = malloc(5 * sizeof(*d));
assert(d != NULL, "Memory allocation failed");
diff = &d[5] - &d[0];        // Well-defined

根据Jonathan Leffler comment的指示,这对于动态分配的内存有效的另一个原因是:

§7.22.3(p1)

  

未指定连续调用aligned_alloccallocmallocrealloc函数分配的存储的顺序和连续性。如果分配成功,则返回指针,以便可以将其分配给指向具有基本对齐要求的任何类型对象的指针,然后用于访问此类对象或此类对象的数组在分配的空间中(直到空间被明确释放)。

上面代码段中malloc返回的指针被分配给d,分配的内存是5 int个对象的数组。

答案 2 :(得分:7)

是的,相同的规则适用于具有动态和自动存储持续时间的变量。它甚至适用于单个元素的malloc请求(标量相当于这方面的单元素数组)。

指针算法仅在数组中有效,包括一个超过数组末尾的数组。

在取消引用时,需要注意一个重要因素:对于初始化int a[5] = {0};,编译器不得尝试在表达式中取消引用 a[5] int* p = &a[5];它必须将其编译为int* p = a + 5;同样,同样的事情也适用于动态存储。

答案 3 :(得分:7)

  

使用指向one-past-malloc的指针是否明确定义?

是的,但是存在一个极端情况,其中定义良好:

void foo(size_t n) {
  int *a = malloc(n * sizeof *a);
  assert(a != NULL || n == 0, "Memory allocation failed");
  int *p = a+n;
  intptr_t diff = p-a;
  ...
}
  

内存管理函数 ...如果请求的空间大小为零,则行为是实现定义的:返回空指针,或者行为就像大小是某些非零值,但返回的指针不得用于访问对象。 C11dr§7.22.31

foo(0) - > malloc(0)可能会返回NULLnon-NULL。在第一个实现中,NULL的返回不是“内存分配失败”。 这意味着代码正在int *p = NULL + 0;尝试使用int *p = a+n;,这会使指针数学失败 - 或者至少会对这些代码产生疑问。

通过避免0大小分配,便携式代码受益。

void bar(size_t n) {
  intptr_t diff;
  int *a;
  int *p;
  if (n > 0) {
    a = malloc(n * sizeof *a);
    assert(a != NULL, "Memory allocation failed");
    p = a+n;
    diff = p-a;
  } else {
    a = p = NULL;
    diff = 0;
  }
  ...
}