当内存来自 malloc 时,将 char * 转换为另一种指针类型是否会违反严格的别名规则?

问题描述 投票:0回答:1

读到

char *
- 以及它们的有符号和无符号对应物 - 可以在不违反严格别名规则的情况下为任何类型别名。但是,让
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 的任何警告,但显然当你违反严格的别名规则时,它警告你的方式不太可靠。那么我的例子会破坏它们吗?

c strict-aliasing
1个回答
0
投票

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

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

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

另一方面,不能保证

ptr+1
double
正确对齐的指针。事实上,只要我们知道
ptr
本身已正确对齐,它很可能没有对齐。

下一个问题是,如果我们更改代码以使指针正确对齐,您的问题的答案是什么。

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;
}

对于上述修改后的代码,假设没有对分配的缓冲区进行出界访问,则行为是明确定义的。这个从cppreference中对应的描述可以看出:

如果对象是由分配函数创建的(包括 realloc),它没有声明类型。这样的对象获得了有效的 输入如下:

  • 第一次通过类型为 other 的左值写入该对象 不是字符类型,此时该左值的类型变为 该对象对于该写入和所有后续读取的有效类型。
  • memcpy 或 memmove 将另一个对象复制到该对象中,或者复制 另一个对象作为字符类型数组放入该对象中,位于 源对象的有效类型(如果有的话) 成为该对象对于该写入和所有的有效类型 后续阅读。
  • 对未声明的对象的任何其他访问 类型,有效类型是用于的左值的类型 访问。

在标记为

(*)
的行,我们点击了上面引号中的第一个项目符号,对于现在位于
double
地址的
ptr+_Alignof(double)
对象,有效类型固定为double。

即使指针正确对齐,

(*)
之后的以下代码也会违反严格别名规则。

int i = *((int *)(ptr+_Alignof(double));
© www.soinside.com 2019 - 2024. All rights reserved.