C 标准是否允许为指针分配任意值并递增它?

Does the C standard permit assigning an arbitrary value to a pointer and incrementing it?

这段代码的行为是否定义明确?

#include <stdio.h>
#include <stdint.h>

int main(void)
{
    void *ptr = (char *)0x01;
    size_t val;

    ptr = (char *)ptr + 1;
    val = (size_t)(uintptr_t)ptr;

    printf("%zu\n", val);
    return 0;
}

我的意思是,即使指针指向某个随机地址,我们是否可以将一些固定数字分配给指针并递增它? (我知道你不能取消引用它)

否,此程序的行为未定义。一旦在程序中达到未定义的构造,任何未来的行为都是未定义的。矛盾的是,任何过去的行为也是不确定的。

void *ptr = (char*)0x01; 的结果是实现定义的,部分原因是 char 可以有陷阱表示。

但是语句 ptr = (char *)ptr + 1; 中随后的指针算法的行为是 未定义。这是因为指针运算仅在数组内有效,包括数组末尾之后的数组。为此,对象是长度为 1 的数组。

作业:

void *ptr = (char *)0x01;

实现定义的行为因为它正在将整数转换为指针。这在 C standard 关于指针的第 6.3.2.3 节中有详细说明:

5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

至于后续的指针运算:

ptr = (char *)ptr + 1;

这取决于几件事。

首先,ptr 的当前值可能 是上述 6.3.2.3 中的陷阱表示。如果是,则行为是 undefined

接下来是0x1是否指向有效对象的问题。仅当指针操作数和结果都指向数组对象的元素(单个对象算作大小为 1 的数组)或数组对象后面的一个元素时,添加指针和整数才有效。这在第 6.5.6 节中有详细说明:

7 For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type

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. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P) ) and (P)-N (where N has the value n ) point to, respectively, the i+n-th and i−n-th elements of the array object, provided they exist. 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 both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. 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.

在托管实现中,值 0x1 几乎可以肯定 not 指向一个有效对象,在这种情况下添加是 undefined。然而,嵌入式实现可以支持将指针设置为特定值,如果是这样的话,0x1 实际上可能指向一个有效对象。如果是,则行为是 明确定义,否则是 未定义

是的,代码定义明确,是实现定义的。它不是未定义的。参见 ISO/IEC 9899:2011 [6.3.2.3]/5 和注释 67。

C 语言最初是作为系统编程语言创建的。系统编程需要操纵内存映射硬件,需要将硬编码地址填充到指针中,有时会增加这些指针,并从结果地址读取数据和向结果地址写入数据。为此,语言明确定义了将整数赋值给指针并使用算术操作该指针。通过实现定义,该语言允许发生各种事情:从经典的暂停和捕获火到在尝试取消引用奇数地址时引发总线错误。

未定义行为和实现定义行为的区别基本上是未定义行为意味着"don't do that, we don't know what will happen"和实现定义行为意味着"it's OK to go ahead and do that, it's up to you to know what will happen."

这是未定义的行为。

来自 N1570(添加了重点):

An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

如果该值是陷阱表示,则读取它是未定义的行为:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.) Such a representation is called a trap representation.

An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue) or a function (in which case it is a function designator).

因此,在 (char*)0x01(void*)(char*)0x01 是陷阱表示的实现中,行 void *ptr = (char *)0x01; 已经是潜在的未定义行为。左边是一个没有字符类型的左值表达式,读取一个陷阱表示。

在某些硬件上,将无效指针加载到机器寄存器中可能会使程序崩溃,因此这是标准委员会的强制举措。

标准不要求实现以有意义的方式处理任何特定整数值的整数到指针的转换,甚至不要求空指针常量以外的任何可能的整数值。对于这种转换,它唯一能保证的是,如果程序将这种转换的结果直接存储到合适的指针类型的对象中,并且除了检查该对象的字节外什么都不做,最坏的情况下,将看到未指定的值。虽然将整数转换为指针的行为是实现定义的,但没有什么可以禁止 any 实现(无论它实际上对这种转换做了什么!)指定一些(甚至所有) ) 具有未指定值的表示的字节,并指定一些(甚至所有)整数值可能表现得好像它们产生陷阱表示一样。

标准对整数到指针的转换有任何规定的唯一原因是:

  1. 在某些实现中,构造是有意义的,并且这些实现的某些程序需要它。

  2. 标准的作者不喜欢在某些实现中使用的构造对其他实现表示违反约束的想法。

  3. 如果标准描述了一个构造,然后指定它在所有情况下都具有未定义的行为,那会很奇怪。

就个人而言,我认为标准应该允许实现将整数到指针的转换视为约束违规,如果它们没有定义它们有用的任何情况,而不是要求编译器接受无意义的代码,但这不是当时的理念。

我认为最简单的说法是任何涉及从指针到整数转换接收到的 intptr_t 或 uintptr_t 值以外的任何整数到指针转换的操作都会调用未定义的行为,但请注意,用于低级编程的质量实现通常会处理未定义的行为 "in a documented manner characteristic of the environment"。该标准未指定实施应何时处理以这种方式调用 UB 的程序,而是将其视为实施质量问题。

如果实现指定整数到指针的转换以定义

行为的方式运行
char *p = (char*)1;
p++;

等同于 "char p = (char)2;",那么应该期望实现以这种方式工作。另一方面,一个实现可以定义整数到指针转换的行为,甚至可以:

char *p = (char*)1;
char *q = p;  // Not doing any arithmetic here--just a simple assignment

会放鼻魔。在大多数平台上,由整数到指针转换产生的指针算术行为异常的编译器不会被视为适合低级编程的高质量实现。因此,不打算以任何其他类型的实现为目标的程序员可以期望此类构造在代码适用的编译器上表现有用,即使标准不要求它。