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

iBu*_*Bug 47 c malloc bounds language-lawyer dynamic-allocation

在C,这是非常清楚,使指向一个指向一个过去的数组的最后一个元素,并在指针算术使用它,只要你不取消对它的引用:

int a[5], *p = a+5, diff = p-a; // Well-defined
Run Code Online (Sandbox Code Playgroud)

但是,这些是UB:

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

现在我有一个问题:这是否适用于动态分配的内存?假设我只是在指针算术中使用指向一个过去的指针,而不取消引用它,并且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?
Run Code Online (Sandbox Code Playgroud)

Ser*_*sta 26

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

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

由于子句中的内存类型从未被精确定义,因此它适用于任何类型的内存,包括已分配的内存.

这显然意味着:

int *a = malloc(5 * sizeof(*a));
assert(a != NULL, "Memory allocation failed");
Run Code Online (Sandbox Code Playgroud)

int *p = a+5;
int diff = p-a;
Run Code Online (Sandbox Code Playgroud)

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

  • @iBug是的,你不能指望它能运作.*"如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出;**否则,行为未定义**"* (3认同)

hac*_*cks 23

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

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

n1570 - §6.5.6(p8):

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

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

(第9页):

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

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

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
Run Code Online (Sandbox Code Playgroud)

正如Jonathan Leffler评论中指出的那样,这对于动态分配的内存有效的另一个原因是:

§7.22.3 (p1):

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

malloc上面片段返回的指针被分配给,d并且分配的内存是一个包含5个int对象的数组.

  • 形式上,`d`指向的数据如何最终成为一个数组?根据C标准,malloc:ed数据的有效类型是用于左值访问的数据.这是`int`,而不是`int [5]`. (2认同)
  • @伦丁;不,事实并非如此。“d”是一个指针,指向“malloc”分配的内存块的第一个块。 (2认同)

Bat*_*eba 7

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

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

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

  • 我试图说没有UB表达式&a [5],因为编译器必须将它视为+ 5.它是否读得不好?经过一个周末的实施后我感冒了:https://meta.stackexchange.com/questions/303920/winter-bash-2017-counting-down-page-whats-with-the-fence/303921#303921 (3认同)

chu*_*ica 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;
  ...
}
Run Code Online (Sandbox Code Playgroud)

内存管理函数 ...如果请求的空间大小为零,则行为是实现定义的:要么返回空指针,要么行为就像大小是非零值一样,除了返回的指针不应该用于访问对象.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;
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

  • @ machine_1 - 我猜想在编写(第一个)标准时已经存在两种替代实现. (2认同)