严格的别名和内存位置

thi*_*his 15 c memory standards strict-aliasing

严格别名会阻止我们使用不兼容的类型访问相同的内存位置.

int* i = malloc( sizeof( int ) ) ;  //assuming sizeof( int ) >= sizeof( float )
*i = 123 ;
float* f = ( float* )i ;
*f = 3.14f ;
Run Code Online (Sandbox Code Playgroud)

根据C标准,这将是非法的,因为编译器"知道" 左值int不能访问float.

如果我使用该指针指向正确的内存,如下所示:

int* i = malloc( sizeof( int ) + sizeof( float ) + MAX_PAD ) ;
*i = 456 ;
Run Code Online (Sandbox Code Playgroud)

首先,我为内存分配内存int,float最后一部分是允许float存储在对齐地址的内存.float需要在4的倍数上对齐,MAX_PAD通常是16个字节中的8个,具体取决于系统.在任何情况下,MAX_PAD足够大,所以float可以正确对齐.

然后,我写的int进入i,到目前为止,一切顺利.

float* f = ( float* )( ( char* )i + sizeof( int ) + PaddingBytesFloat( (char*)i ) ) ;
*f= 2.71f ;
Run Code Online (Sandbox Code Playgroud)

我使用指针i,用它的大小递增它,int并使用函数正确对齐它,该函数PaddingBytesFloat()返回float给定地址所需的对齐a所需的字节数.然后我写了一个浮点数.

在这种情况下,f指向不重叠的不同内存位置; 它有不同的类型.


以下是标准(ISO/IEC 9899:201x)6.5中的一些部分,我在编写此示例时依赖于此.

别名是指多个左值指向同一个内存位置.标准要求这些左值具有与对象的有效类型兼容的类型.

什么是有效类型,引自标准:

用于访问其存储值的对象的有效类型是对象的声明类型(如果有).87)如果通过具有非字符类型的左值的值将值存储到没有声明类型的对象中,然后左值的类型成为该访问的对象的有效类型以及不修改存储值的后续访问.如果使用memcpy或memmove将值复制到没有声明类型的对象中,或者将其复制为字符类型数组,则该访问的修改对象的有效类型以及不修改该值的后续访问的有效类型是复制值的对象的有效类型(如果有).对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型.

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

我正在尝试连接各个部分并弄清楚是否允许这样做.在我的解释中,分配对象的有效类型可以根据该内存上使用的左值的类型进行更改,因为这部分:对于没有声明类型的对象的所有其他访问,对象的有效类型就是用于访问的左值的类型.

这合法吗?如果没有,如果我i在第二个例子中使用void指针作为lvalue而不是int指针怎么办?如果即使这样也行不通,如果我将第二个例子中指定给浮点指针的地址作为memcopied值,并且该地址以前从未用作左值,该怎么办?

rod*_*igo 10

我认为是的,这是合法的.

为了说明我的观点,让我们看看这段代码:

struct S
{
    int i;
    float f;
};
char *p = malloc(sizeof(struct S));

int *i = p + offsetof(struct S, i);  //this offset is 0 by definition
*i = 456;
float *f = p + offsetof(struct S, f);
*f= 2.71f;
Run Code Online (Sandbox Code Playgroud)

这个代码,IMO,显然是合法的,从编译器的角度来看,它与您的代码相同,适用于PaddingBytesFloat()和的值MAX_PAD.

请注意,我的代码不使用任何类型的l值struct S,它仅用于简化填充的计算.

当我阅读标准时,在malloc的内存中没有声明的类型,直到写入那里.然后声明的类型是写入的内容.因此,可以随时更改此类内存的声明类型,使用不同类型的值覆盖内存,非常类似于union.

TL; DR:我的结论是,对于动态内存,只要您使用用于上次写入该内存的相同类型(或兼容的类型)读取内存,就可以安全地使用严格别名.

  • @DAhrens:不.代码是`p + offset(...)`,`p`是指向char的指针.指向浮点数的强制转换在后面的赋值中隐式完成. (2认同)
  • @dave:有一件事是使用`char`左值来访问`int`有效类型的对象.另一种方法是使用`int`来访问`char`有效类型的对象.根据引用标准的6.5.7允许前者.后者不是. (2认同)

Fil*_*ves 9

是的,这是合法的.要了解原因,您甚至不需要考虑严格的别名规则,因为它不适用于这种情况.

根据C99标准,当你这样做时:

int* i = malloc( sizeof( int ) + sizeof( float ) + MAX_PAD ) ;
*i = 456 ;
Run Code Online (Sandbox Code Playgroud)

malloc将返回一个指向内存块的指针,该内存块足以容纳一个大小的对象sizeof(int)+sizeof(float)+MAX_PAD.但请注意,您只使用这么大的一小块; 特别是,您只使用第一个sizeof(int)字节.因此,您将留下一些可用于存储其他对象的可用空间,只要将它们存储为不相交的偏移量(即在第一个sizeof(int)字节之后).这与对象究竟是什么的定义密切相关.从C99第3.14节:

对象:执行环境中的数据存储区域,其内容可以表示值

指向的对象内容的确切含义i是值456; 这意味着整数对象本身只占用您分配的内存块的一小部分.标准中没有任何内容阻止您存储前面几个字节的任何类型的新的不同对象.

这段代码:

float* f = ( float* )( ( char* )i + sizeof( int ) + PaddingBytesFloat( (char*)i ) ) ;
*f= 2.71f ;
Run Code Online (Sandbox Code Playgroud)

是否有效地将另一个对象附加到已分配内存的子块.只要产生的内存位置f不与其重叠i,并且还有足够的空间来存储float,您将始终是安全的.严格别名规则在这里甚至不适用,因为指针指向不重叠的对象 - 内存位置不同.

我认为这里的关键点是要理解你正在有效地操纵两个不同的对象,有两个不同的指针.恰好两个指针都指向相同的malloc()'d块,但它们彼此足够远,所以这不是问题.

您可以查看此相关问题:哪些对齐问题限制了malloc创建的内存块的使用?并阅读Eric Postpischil的好答案:https://stackoverflow.com/a/21141161/2793118 - 毕竟,如果你可以在同一个malloc()块中存储不同类型的数组,为什么不存储一个int和一个float?您甚至可以将代码视为这些数组是单元素数组的特殊情况.

只要您处理对齐问题,代码就完美无缺,100%便携.

更新(后续行动,阅读以下评论):

我相信你对标准没有对malloc()'d对象强制执行严格别名的推理是错误的.确实可以改变动态分配对象的有效类型,如标准所传达的(这是使用不同类型的左值表达式来存储新值),但请注意,一旦你做了那就是,确保没有其他类型的其他左值表达式访问对象值是你的工作.这由第6.5节的规则7强制执行,并在您的问题中引用它:

对象的存储值只能由具有以下类型之一的左值表达式访问: - 与对象的有效类型兼容的类型;

因此,当您更改对象的有效类型时,您隐含地向编译器承诺,您将不使用具有不兼容类型的旧指针访问此对象(与新的有效类型相比).对于严格别名规则而言,这应该足够了.