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

Is it well-defined to use a pointer pointing to one-past-malloc?

在 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?

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

§ 8 When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. ... Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object... If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

由于子句中从未明确指出内存的类型,因此它适用于任何类型的内存,包括已分配的内存。

这显然意味着之后:

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

两个

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

被完美定义,并且作为通常的指针算术规则适用,diff 应接收值 5

Is it well-defined to use a pointer pointing to one-past-malloc?

如果 p 指向已分配的内存并且未取消引用,则定义明确。

n1570 - §6.5.6 (p8):

[...] If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

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

(p9):

When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object [...]

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

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 in a 所指出的,这对动态分配的内存有效的另一个原因是:

§7.22.3 (p1)

The order and contiguity of storage allocated by successive calls to the aligned_alloc, calloc, malloc, and realloc functions is unspecified. The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated).

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

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

指针运算仅在数组内有效,包括数组末尾之后的数组。

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

Is it well-defined to use a pointer pointing to 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;
  ...
}

Memory management functions ... If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object. C11dr §7.22.3 1

foo(0) --> malloc(0) 可能 return 一个 NULLnon-NULL。在第一个实现中,NULL 的 return 不是 "Memory allocation failure"。 这意味着代码正在尝试 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;
  }
  ...
}