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

Dav*_*eri 52 c pointers pointer-arithmetic language-lawyer

这段代码的行为是否定义得很好?

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

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

dbu*_*ush 68

分配:

void *ptr = (char *)0x01;
Run Code Online (Sandbox Code Playgroud)

实现定义的行为,因为它将整数转换为指针.关于指针的C标准第6.3.2.3节详细说明了这一点:

5整数可以转换为任何指针类型.除非先前指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示.

至于后续的指针算术:

ptr = (char *)ptr + 1;
Run Code Online (Sandbox Code Playgroud)

这取决于一些事情.

首先,当前值ptr 可以是根据上述6.3.2.3的陷阱表示.如果是,则行为未定义.

接下来是0x1指向有效对象的问题.添加一个指针和一个整数才有效,如果这两个指针操作数和结果指向数组对象的元素(一个对象计为大小1的阵列)或过去数组对象的一个元素.详见6.5.6节:

7出于这些运算符的目的,指向不是数组元素的对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型

8当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型.如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式.换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第i-n个元素,只要它们存在.此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素. 如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义. 如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数.

在托管实现价值0x1几乎肯定是没有指向一个有效的对象,在这种情况下,除了是不确定的.然而,嵌入式实现可以支持设置指向特定值的指针,如果是这样的话,0x1实际上可能指向有效对象.如果是,则行为定义良好,否则未定义.

  • @GoswinvonBrederlow:我不认为允许实现定义的行为是未定义的.但我知道你要说的是 - 添加是否是未定义的行为取决于实现定义的选择. (3认同)
  • 如果OP没有添加"+ 1",那么你对p8的引用是相关的.但是......需要考虑[p7](https://port70.net/~nsz/c/c11/n1570.html#6.5.6p7). (2认同)
  • 我认为指针算法是实现定义的,因为:赋值是实现定义的.如果实现导致指向char数组内部的指针,则定义了`ptr + 1`.另一方面,如果实现返回例如陷阱,则它是UB. (2认同)
  • 严格来说,这不能在具体实施中明确定义吗?即如果`(int*)0x01`是由我的虚构实现WeirdC定义为指向一个有效的char数组,那么它是否会使其余的有效,尽管只在WeirdC上?我问,因为这与例如嵌入式开发相关,您可能正在使用指向某个给定整数值的指针来修改数据. (2认同)

Bat*_*eba 18

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

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

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


Ste*_*ebb 8

是的,代码被明确定义为实现定义.它没有未定义.见ISO/IEC 9899:2011 [6.3.2.3]/5和注67.

C语言最初是作为系统编程语言创建的.系统编程需要操作内存映射硬件,要求您将硬编码地址填充到指针中,有时会增加这些指针,并从结果地址读取和写入数据.为此,为指针分配和整数并使用算术操纵该指针由语言很好地定义.通过使其实现定义,什么语言可以是各种事情都可能发生:从经典停止-和追赶火试图取消引用奇数地址时,提高了总线错误.

未定义行为和实现定义行为之间的区别基本上是未定义的行为意味着"不要那样做,我们不知道会发生什么",而实现定义的行为意味着"可以继续这样做,这取决于你知道会发生什么."

  • 语言标准明确定义指针算法仅在对象的边界内有效 (4认同)
  • "使用算术操纵指针是由语言很好地定义" - 引用需要. (2认同)
  • 我认为很明显这是C标准中的UB(因为通常在任何对象的边界之外创建指针),但是许多实现将在更多情况下定义指针的行为而不是C标准.例如,如果你没有取消引用它们,在许多实现中,创建不指向任何对象内部的指针是安全的. (2认同)

Dav*_*lor 8

这是未定义的行为.

从N1570开始(重点补充):

整数可以转换为任何指针类型.除非先前指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示.

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

某些对象表示不需要表示对象类型的值.如果对象的存储值具有这样的表示并且由不具有字符类型的左值表达式读取,则行为是未定义的.如果这样的表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式修改对象的全部或任何部分,则行为是未定义的.这种表示称为陷阱表示.

标识符是主表达式,前提是它已被声明为指定一个对象(在这种情况下它是一个左值)或一个函数(在这种情况下它是一个函数指示符).

因此,该行void *ptr = (char *)0x01;已经是潜在的未定义行为,在其中(char*)0x01或是(void*)(char*)0x01陷阱表示的实现上.左侧是左值表达式,它没有字符类型并读取陷阱表示.

在某些硬件上,将无效指针加载到机器寄存器可能会导致程序崩溃,因此这是标准委员会的强制移动.