我一直在尝试理解严格的别名规则,因为它们适用于char指针.
这里说明:
始终假定char*可以引用任何对象的别名.
好的,在套接字代码的上下文中,我可以这样做:
struct SocketMsg
{
int a;
int b;
};
int main(int argc, char** argv)
{
// Some code...
SocketMsg msgToSend;
msgToSend.a = 0;
msgToSend.b = 1;
send(socket, (char*)(&msgToSend), sizeof(msgToSend);
};
Run Code Online (Sandbox Code Playgroud)
但接下来是这个声明
相反的情况并非如此.将char*转换为除char*之外的任何类型的指针并取消引用它通常违反严格别名规则.
这是否意味着当我收回一个char数组时,当我知道消息的结构时,我无法重新解释转换为结构:
struct SocketMsgToRecv
{
int a;
int b;
};
int main()
{
SocketMsgToRecv* pointerToMsg;
char msgBuff[100];
...
recv(socket, msgBuff, 100);
// Ommiting make sure we have a complete message from the stream
// but lets assume msgBuff[0] has a complete msg, and lets interpret …Run Code Online (Sandbox Code Playgroud) 代码1:
unsigned int *p = malloc(sizeof *p);
memset(p, 0x55, sizeof *p);
unsigned int u = *p;
Run Code Online (Sandbox Code Playgroud)
代码2:
void *d = malloc(50);
*(double *)d = 1.23;
memset(d, 0x55, 50);
unsigned int u = *(unsigned int *)d;
Run Code Online (Sandbox Code Playgroud)
在每种情况下,memset对malloc空间中对象的有效类型有什么影响; 那么初始化u正确还是严格的别名违规?
有效类型的定义(C11 6.5/6)是:
用于访问其存储值的对象的有效类型是对象的声明类型(如果有).如果通过具有非字符类型的左值的值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该值的后续访问的有效类型储值.如果使用
memcpy或将值复制到没有声明类型的对象中memmove,或者将其复制为字符类型数组,则该访问的修改对象的有效类型以及不修改该值的后续访问的有效类型是有效类型复制值的对象,如果有的话.对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型.
然而,目前还不清楚是否memset表现得像通过字符类型的左值或其他东西写作.memset(7.24.6.1)的描述并不是很有启发性:
memset函数将c的值(转换为a
unsigned char)复制到s指向的对象的前n个字符中.
示例代码:
struct S { int x; };
int func()
{
S s{2};
return (int &)s; // Equivalent to *reinterpret_cast<int *>(&s)
}
Run Code Online (Sandbox Code Playgroud)
我认为这很常见,被认为是可以接受的.该标准确保结构中没有初始填充.但是,这种情况未在严格别名规则(C++ 17 [basic.lval]/11)中列出:
如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:
- (11.1)对象的动态类型,
- (11.2)对象的动态类型的cv限定版本,
- (11.3)与对象的动态类型类似(如7.5中所定义)的类型,
- (11.4)与对象的动态类型对应的有符号或无符号类型的类型,
- (11.5)一种类型,它是有符号或无符号类型,对应于对象动态类型的cv限定版本,
- (11.6)聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(递归地,包括子聚合或包含联合的元素或非静态数据成员),
- (11.7)一种类型,它是对象的动态类型的(可能是cv限定的)基类类型,
- (11.8)char,unsigned char或std :: byte类型.
很明显,对象s正在访问其存储值.
项目符号点中列出的类型是执行访问的glvalue的类型,而不是被访问对象的类型.在这段代码中,glvalue类型int不是聚合类型或联合类型,排除了11.6.
我的问题是:这个代码是否正确,如果是,那么允许上述哪一个要点?
我试图从浮点数中提取位而不调用未定义的行为.这是我的第一次尝试:
unsigned foo(float x)
{
unsigned* u = (unsigned*)&x;
return *u;
}
Run Code Online (Sandbox Code Playgroud)
据我了解,由于严格的别名规则,这不能保证工作,对吧?如果使用字符指针进行中间步骤,它是否有效?
unsigned bar(float x)
{
char* c = (char*)&x;
unsigned* u = (unsigned*)c;
return *u;
}
Run Code Online (Sandbox Code Playgroud)
或者我是否必须自己提取单个字节?
unsigned baz(float x)
{
unsigned char* c = (unsigned char*)&x;
return c[0] | c[1] << 8 | c[2] << 16 | c[3] << 24;
}
Run Code Online (Sandbox Code Playgroud)
当然,这有一个缺点,取决于字节顺序,但我可以忍受.
工会黑客肯定是未定义的行为,对吧?
unsigned uni(float x)
{
union { float f; unsigned u; };
f = x;
return u;
}
Run Code Online (Sandbox Code Playgroud)
为了完整起见,这里有一个参考版本foo.也是未定义的行为,对吗?
unsigned ref(float x)
{ …Run Code Online (Sandbox Code Playgroud) restrictC++中缺少C中的关键字,所以出于兴趣,我一直在寻找一种在C++中模拟相同功能的方法.
具体来说,我希望以下内容相同:
// C
void func(S *restrict a, S *restrict b)
// C++
void func(noalias<S, 1> a, noalias<S, 2> b)
Run Code Online (Sandbox Code Playgroud)
哪里 noalias<T, n>
T*用->和访问时一样*T*(这样函数可以被称为func(t1, t2),where t1和t2are都是类型T*)n指定变量的"混叠类",使类型的变量noalias<T, n>和noalias<T, m>可从未假定为别名对于n!=米.这是我的严重缺陷解决方案:
template <typename T, int n>
class noalias
{
struct T2 : T {};
T *t;
public:
noalias(T *t_) : t(t_) {}
T2 *operator->() const {return static_cast<T2*>(t);} …Run Code Online (Sandbox Code Playgroud) WG14 成员 Jens Gustedt 在一篇关于严格别名规则的博文中说:
字符数组不能被重新解释为其他类型的对象。
事实上,这是真的吗?(我猜标准中的相应语言是说如果一个对象具有声明的类型,那么该类型就是它的有效类型。)如果是这样,这是否意味着分配器从静态声明的内存区域中分配内存是不可实现的标准C?
我知道 TeX 忽略了 Pascal 的大部分类型系统,并且由于类似的问题将所有内容都视为单词数组,但我希望如果我在 ( malloc-less) C 中发现自己处于类似的情况,我可以声明一个最大对齐char数组并继续以通常的方式使用结构。_Alignas除了作为表达非标准要求的标准化设备(类似于volatile)之外,我也看不出在这样的世界中可能有什么意义。
c standards memory-management strict-aliasing language-lawyer
我最近遇到了严格的别名规则,但是我无法理解如何在void *不违反规则的情况下执行类型惩罚.
我知道这违反了规则:
int x = 0xDEADBEEF;
short *y = (short *)&x;
*y = 42;
int z = x;
Run Code Online (Sandbox Code Playgroud)
而且我知道我可以安全地使用C99中的联合进行类型惩罚:
union{
int x;
short y;
} data;
data.x = 0xDEADBEEF;
data.y = 42;
int z = data.x;
Run Code Online (Sandbox Code Playgroud)
但是如何void *在C99中安全地执行打字?以下是正确的:
int x = 0xDEADBEEF;
void * helper = (void *)&x;
short *y = (short *)helper;
*y = 42;
int z = x;
Run Code Online (Sandbox Code Playgroud)
我怀疑代码仍然会破坏严格的别名规则,因为变量x地址的内存可以由两者修改x并且可以解除引用y.
如果未定义类型 - 惩罚void *,那么void * …
我正在尝试检查我的一些代码是否存在严格的别名违规,但在尝试理解严格的别名规则时,我似乎错过了一些东西.
想象一下以下代码:
#include <stdio.h>
int main( void )
{
unsigned long l;
l = 0;
*( ( unsigned short * )&l ) = 1;
printf( "%lu\n", l );
return 0;
}
Run Code Online (Sandbox Code Playgroud)
经典和基本的例子.使用GCC 4.9(-Wall -fstrict-aliasing -Wstrict-aliasing -O3),它实际报告错误:
dereferencing type-punned pointer will break strict-aliasing rules
Run Code Online (Sandbox Code Playgroud)
但以下编译很好:
#include <stdio.h>
int main( void )
{
unsigned long l;
unsigned short * sp;
l = 0;
sp = ( unsigned short * )&l;
*( sp ) = 1;
printf( "%lu\n", l );
return …Run Code Online (Sandbox Code Playgroud) 假设unsigned int没有陷阱表示,请执行下面标记为(A)和(B)的语句中的任何一个或两个引发未定义的行为,为什么或为什么不这样做,以及(特别是如果您认为其中一个定义明确但另一个不定义),您认为标准中存在缺陷吗?我主要对当前版本的C标准(即C2011)感兴趣,但如果在标准的旧版本或C++中有所不同,我也想知道这一点.
(_Alignas在这个程序中用于消除因对齐不充分而导致的任何UB问题.我在解释中讨论的规则虽然没有说明对齐.)
#include <stdlib.h>
#include <string.h>
int main(void)
{
unsigned int v1, v2;
unsigned char _Alignas(unsigned int) b1[sizeof(unsigned int)];
unsigned char *b2 = malloc(sizeof(unsigned int));
if (!b2) return 1;
memset(b1, 0x55, sizeof(unsigned int));
memset(b2, 0x55, sizeof(unsigned int));
v1 = *(unsigned int *)b1; /* (A) */
v2 = *(unsigned int *)b2; /* (B) */
return !(v1 == v2);
}
Run Code Online (Sandbox Code Playgroud)
我对C2011的解释是(A)引发未定义的行为,但(B)定义明确(存储未指定的值v2),因为:
memset被定义(§7.24.6.1)写入它的第一个参数作为-如果通过与字符类型,它被允许用于两左值b1和b2每特殊情况下,在§6.5p7的底部.
该对象b1具有声明的类型unsigned char[n].因此,它的有效访问类型也是unsigned char[n] …