I readchar *以及它们的有符号和无符号对应物可以在不违反严格的别名规则的情况下为任何类型别名.但是,将char *指向int变量并将char *转换为double *违反了规则,因为基础对象的类型为int.但如果记忆来自malloc呢?例如:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    void *buffer = malloc(32);
    unsigned char *ptr = buffer;

    *ptr = 10;
    *((double *)(ptr + 1)) = 3.14;
    *((double *)(ptr + 9)) = 2.718;

    printf("*ptr: %d\n", *ptr);
    printf("*(ptr + 1): %lf\n", *((double *)(ptr + 1)));
    printf("*(ptr + 9): %lf\n", *((double *)(ptr + 9)));
    
    return 0;
}

这将打印以下内容:

*ptr: 10
*(ptr + 1): 3.140000
*(ptr + 9): 2.718000

如果我错了,请纠正我,但据我所知,来自Malloc的内存是非类型化的,并且可以存储任何数据,而不像int数组只能存储int类型的数据.

我还没有收到任何来自gcc的警告,但很明显,当你违反了严格的别名规则时,它警告你并不是很可靠.我的榜样会打破它们吗?

推荐答案

由于未对齐的指针进行无效访问,您的代码具有未定义的行为.

malloc返回的指针的内存地址被指定为具有最大对齐,以便该地址可以由不同类型使用而不会出现问题.这就是为什么这样做是合理的

double* ptr = malloc(42*sizeof(double));

另一方面,不能保证ptr+1double的正确对齐的指针.事实上,它很可能没有对齐,只要我们知道ptr本身是正确对齐的,正如您可以看到报告的未定义行为是here.

下一个问题是,如果我们改变代码,使指针正确对齐,你的问题的答案是什么.

int main(void)
{
    void *buffer = malloc(32);
    unsigned char *ptr = buffer;

    *ptr = 10;
    *((double *)(ptr + _Alignof(double))) = 3.14; //(*)

    printf("*ptr: %d\n", *ptr);
    printf("*(ptr + _Alignof(double)): %lf\n", *((double *)(ptr + _Alignof(double))));
    
    return 0;
}

对于上面修改的代码,行为是定义良好的,假设对分配的缓冲区没有越界访问.这可以从以下引用的最新标准草案(6.5.6 in https://open-std.org/JTC1/SC22/WG14/www/docs/n3096.pdf)中看出:

访问其存储值的对象的有效类型为 对象的声明类型(如果有)98.如果将值存储到 没有声明类型的对象,而左值具有 不是非原子字符类型,则左值的类型 成为该访问的对象的有效类型 不修改存储值的后续访问.如果值为 使用Memcpy或MemMove复制到没有声明类型的对象中, 或被复制为字符类型的数组,则 该访问和后续访问的已修改对象 不修改的值是从其开始的对象的有效类型 如果有值,则会复制该值.对于所有其他对 对象没有声明类型,则该对象的有效类型为 只是用于访问的左值的类型.

其中脚注98说

分配的对象没有声明类型.

在标记为(*)的行,我们点击上面引号中的第二句,对于现在位于地址ptr+_Alignof(double)double对象,有效类型固定为DOUBLE.

即使指针正确对齐,(*)之后的代码也会违反严格别名规则.

int i = *((int *)(ptr+_Alignof(double)));

根据上面引用的最后一句话,允许通过不同类型的左值向内存写入,并且通过明确提到"不修改所存储的值的后续访问"来非常清楚地表明其意图.事实上,这是内存池如何工作的基础,它通过回收其他对象先前使用的内存,并将它们重新分配给具有新类型的对象.因此,以下内容是有效的,并更新了关联内存的有效类型.(假设指针正确对齐)

*((int *)(ptr+_Alignof(double))) = 42;

然而,正如@JohnBollinger在 comments 中指出的那样,许多常见的C实现在通过类似于上面的写操作进行有效的类型更新方面存在缺陷.对于这样的实现,它们可能会执行不正确的类型别名分析并不正确地优化代码.因此,尽管C标准声明上述内容是有效的,但不直接这样做可能更明智.内存池实现的情况是不同的,因为更新有效类型的代码通常位于不同的TU中,并且这种有缺陷的实现的不正确的基于类型的别名分析不会造成太大的伤害.

C++相关问答推荐

我可以动态分配具有空类型函数的矩阵吗?

ATTiny1606定时器TCA 0中断未触发

如何创建由符号组成的垂直结果图形?

如何在Visual Studio代码中关闭此函数名称显示功能?

如何在CANbus RX/TX FIFO起始地址寄存器(ATSAME 51)的特定地址初始化数组?

如何确保在C程序中将包含uft8字符的字符串正确写入MySQL?

如何在提取的索引中分配空值?

致命错误:ASM/rwan ce.h:没有这样的文件或目录.符号链接还不够

添加函数会 destruct 嵌入式C代码(无IDE)

在进程之间重定向输出和输入流的问题

CS50判断灯泡运动的问题,判断时多出一个灯泡,但不在终端上

如果格式字符串的内存与printf的一个参数共享,会发生什么情况?

安全倒计时循环

c程序,让用户输入两类数字,并给出输出用户输入多少个数字

生成的头文件不包括用户定义的文件

在C中使用字符串时是否不需要内存分配?

WSASocket在哪里定义?

在C中定义函数指针?

可以从指针数组中的值初始化指针吗?

将帧从相机 (/dev/video0) 复制到帧缓冲区 (/dev/fb0) 会产生意外结果