void 指针 = int 指针 = float 指针

void pointer = int pointer = float pointer

我有一个指向内存地址的void指针。那么,我

然后,取消引用它们以获取值。

{
    int x = 25;

    void   *p  = &x;
    int    *pi = p;
    float  *pf = p;
    double *pd = p;

    printf("x: n%d\n", x);
    printf("*p: %d\n", *(int *)p);
    printf("*pi: %d\n", *pi);
    printf("*pf: %f\n", *pf);
    printf("*pd: %f\n", *pd);

    return 0;
}

取消引用 piint 指针)的输出是 25。 但是取消引用 pf(float pointer) 的输出是 0.000。 还 dereferncing pd(double pointer) 输出一个负分数,保持 改变?

为什么会这样,是否与字节顺序有关(我的 CPU 是小字节序)?

根据 C 标准,您可以将任何指针转换为 void * 并将其转换回来,效果相同。

引用 C11,章节 §6.3.2.3

[...] A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

这就是为什么当您将 void 指针转换为 int *、取消引用并打印结果时,它会正确打印。

但是,标准不保证您可以将该指针取消引用为不同的数据类型。它本质上是在调用未定义的行为。

因此,取消引用 pfpd 以获得 floatdoubleundefined behavior,因为您正在尝试读取分配的内存对于 int 作为 一个 floatdouble。有一个明显的错误案例导致了 UB。

详细说明,intfloat(以及 double)具有不同的内部表示,因此尝试将指针转换为另一种类型,然后尝试取消引用以获取其他类型 中的值 将不起作用。

相关,C11,章节 §6.5.3.3

[...] If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

对于无效值部分,(强调我的

Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.

除了前面的答案,我认为由于浮点数的表示方式,您所期望的无法实现。

整数通常存储在 Two's complement way, basically it means that the number is stored as one piece. Floats on the other hand are stored using a different way using a sign, base and exponent, Read here.

因此转换的主要思想是不可能的,因为您尝试将一个数字表示为原始位(对于正数)并将其视为编码不同的数字,这将导致意外结果,即使转换是合法。

这里有两种 UB:

1) 严格别名

What is the strict aliasing rule?

"Strict aliasing is an assumption, made by the C (or C++) compiler, that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)"

但是,可以将严格别名作为编译器扩展关闭,例如 GCC 中的 -fno-strict-aliasing。在这种情况下,您的 pf 版本将运行良好,尽管实现已定义,假设没有其他问题(通常 floatint 都是 32 位类型并且在大多数计算机上都是 32 位对齐的, 通常)。如果您的计算机使用 IEEE754 single, you can get a very small denorm floating point 数字,这将解释您观察到的结果。

严格别名是最近版本的 C 的一个有争议的特性(并且被很多人认为是一个错误)并且使得重新解释转换(又名 type punning) C.

在您非常了解类型双关及其与您的编译器和硬件版本的行为方式之前,您应该避免这样做。

2) 内存越界

您的指针指向与 int 一样大的内存 space,但您将其取消引用为 double,这通常是 int 大小的两倍,您基本上是从计算机某处读取了一半 double 的垃圾,这就是为什么您的 double 不断变化的原因。

所以...这可能是正在发生的事情。

However the output of dereferencing pf(float pointer) is 0.000

它不是 0。它真的很小。

你有 4 个字节的整数。你的整数在内存中看起来像这样...

5        0        0        0
00000101 00000000 00000000 00000000

解释为 float 看起来像...

sign  exponent  fraction
   0  00001010  0000000 00000000 00000000
   +   2**-117  * 1.0

所以,您正在输出一个浮点数,但它非常小。它是 2^-117,与 0 几乎没有区别。

如果您尝试使用 printf("*pf: %e\n", *pf); 打印浮点数,那么它应该会给您一些有意义但很小的东西。 7.006492e-45

Also dereferncing pd(double pointer) outputs a negative fraction that keeps changing?

双精度数是 8 个字节,但您只定义了 4 个字节。负分数变化是查看未初始化内存的结果。未初始化内存的值是任意的,看到它随 运行.

变化是正常的

类型 intfloatdouble 具有不同的内存布局、表示和解释。

在我的机器上,int是4个字节,float是4个字节,double是8个字节。

以下是您如何解释所看到的结果。

取消对 int 指针的引用显然有效,因为原始数据是 int.

取消对 float 指针的引用,编译器生成代码将内存中 4 个字节的内容解释为 float。当解释为浮点数时,4 个字节中的值将为您提供 0.00。查找 float 在内存中的表示方式。

取消对 double 指针的引用,编译器生成代码以将内存中的内容解释为 double。因为 doubleint 大,这会访问原始 int 的 4 个字节,以及堆栈上的 extra 4 个字节。因为这些额外的 4 个字节的内容取决于堆栈的状态,并且无法从 运行 运行 中预测,您会看到对应于将整个 8 个字节解释为 [=12] 的不同值=].

接下来,

printf("x: n%d\n", x); //OK
printf("*p: %d\n", *(int *)p); //OK
printf("*pi: %d\n", *pi); //OK
printf("*pf: %f\n", *pf); // UB
printf("*pd: %f\n", *pd); // UB

前 3 个 printfs 中的访问没有问题,因为您通过 int 类型的左值类型访问 int。但是接下来的 2 个并不好,因为违反了 6.5, 7, Expressions.

int * 不是 float *double * 兼容的类型。所以最后两个 printf() 调用中的访问导致 未定义的行为.

C11,6.5 美元,7 个州:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,

— a qualified version of a type compatible with the effective type of the object,

— a type that is the signed or unsigned type corresponding to the effective type of the object,

— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,

— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

— a character type.

术语 "C" 用于描述两种语言:一种由 K&R 发明,其中指针标识物理内存位置,另一种派生自在读取指针和以遵守某些规则的方式编写,但如果以其他方式使用它们可能会以任意方式运行。虽然后一种语言是由标准定义的,但前一种语言是 1980 年代微型计算机编程中流行的语言。

从 C 代码生成高效机器代码的主要障碍之一是编译器无法分辨哪些指针可能别名哪些变量。因此,任何时候代码访问可能指向给定变量的指针时,生成的代码都需要确保指针标识的内存内容与变量的内容相匹配。那可能非常昂贵。编写 C89 标准的人决定应该允许编译器假设命名变量(静态和自动)只能使用它们自己类型或字符类型的指针访问;编写 C99 的人也决定为分配的存储添加额外的限制。

一些编译器提供了代码可以确保使用不同类型的访问将通过内存(或至少表现得好像他们正在这样做)的方法,但不幸的是,我认为没有任何标准。 C14 添加了一个用于多线程的内存模型,它应该能够实现所需的语义,但我认为编译器在可以告诉外部线程无法访问某些东西的情况下不需要尊重这种语义[即使必须通过内存才能实现正确的单线程语义。

如果您正在使用 gcc 并希望内存语义像 K&R 预期的那样工作,请使用“-fno-strict-aliasing”命令行选项。为了提高代码效率,有必要大量使用 C99 中添加的 "restrict" 限定符。虽然 gcc 的作者似乎比 "restrict" 更关注基于类型的别名规则,后者应该允许更有用的优化。