通过结构别名arrays

我正在阅读ISO / IEC 9899:TC2中第6.5段的第7段。

它通过以下方式宽恕对对象的左值访问:

一种聚合或联合类型,包括其成员中的上述类型之一(包括递归地,子聚合或包含联合的成员),

请参阅文档,了解“前面提到的”类型,但它们肯定包含对象的有效类型。

它的部分标注为:

此列表的目的是指定对象可能或可能不具有别名的情况。

我读到这个说(例如)以下是明确定义的:

#include  #include  typedef struct { unsigned int x; } s; int main(void){ unsigned int array[3] = {73,74,75}; s* sp=(s*)&array; sp->x=80; printf("%d\n",array[0]); return EXIT_SUCCESS; } 

该程序应输出80。

我不是在提倡这是一个好的(或非常有用的)想法,并承认我在某种程度上解释它是因为我无法想到其他意味着什么也不能相信它是一个毫无意义的句子!

也就是说,我看不出有理由禁止它。 我们所知道的是该位置的对齐和内存内容与sp->x兼容,为什么不呢?

它似乎甚至可以说我是否加了(比方说) double y; 在结构的末尾,我仍然可以通过sp->x访问array[0]

然而,即使数组大于sizeof(s)任何访问sp->y尝试都是’所有下注’未定义的行为。

可能我礼貌地要求人们说出那句话宽恕而不是进入一个扁平的旋转喊“严格混淆UB严格别名UB”似乎经常是这些事情的方式。

建议中提到了这个问题的答案:我们将会看到修复基于类型的别名的规则 ,但遗憾的是,2010年提出的提案未在2010年11月的Bativa,Htivquist中得到解决。 因此C11不包含N1520的分辨率,所以这是一个悬而未决的问题:

似乎没有办法在这次会议上解决这个问题。 拟议方法的每个主题都会引发更多问题。 2010年11月4日,星期四,上午1:48

行动 – 克拉克做更多的工作

N1520开场说( 强调我的前进 ):

Richard Hansen在基于类型的别名规则中指出了一个问题,如下所示:

我的问题涉及6.5p7的子弹5的措辞(别名,因为它适用于工会/聚合)。 除非我对有效类型的理解不正确,否则似乎联合/聚合条件应该适用于有效类型,而不是左值类型。

以下是一些更多细节:

以下面的代码片段为例:

 union {int a; double b;} u; ua = 5; 

根据我对有效类型定义的理解(6.5p6),对象的有效类型&u是union {int a; 双b;}。 在&u(在第二行)访问对象的左值表达式的类型是int。

根据我对兼容类型(6.2.7)的定义的理解,int与union {int a;不兼容; 双b;},所以6.5p7的子弹1和2不适用。 int不是联合类型的有符号或无符号类型,因此子弹3和4不适用。 int不是字符类型,因此bullet 6不适用。

这留下了子弹5.但是,int不是聚合或联合类型,因此子弹也不适用。 这意味着上面的代码违反了别名规则,显然不应该这样做。

我认为应该重新说明项目符号5,以表明如果有效类型(不是左值类型)是包含类型与左值类型兼容的成员的聚合或联合类型,则可以访问该对象。

实际上,他指出的是规则在结构/联盟成员资格方面是不对称的。 我已经意识到这种情况,并且在相当长的一段时间内将其视为(非紧急)问题。 一系列示例将更好地说明问题。 (这些例子最初是在Santa Cruz会议上提出的。)

根据我对基于类型约束的别名是否有效的问题的经验,问题总是用循环不变性来表达。 这些例子使问题变得非常敏锐。

适用于这种情况的相关示例将是3 ,如下所示:

 struct S { int a, b; }; void f3(int *pi, struct S *ps1, struct S const *ps2) { for (*pi = 0; *pi < 10; ++*pi) { *ps1++ = *ps2; } } 

这里的问题是,是否可以通过分配左值* pi来访问(并特别修改)对象* ps2 - 如果是,则标准是否实际上是这样说的。 可以说,6.5p7的第五个子弹没有涵盖这一点,因为* pi根本没有聚合类型。

也许意图是应该扭转这个问题:是否允许通过左值* ps2访问对象* pi的值。 显然,这个案子将由第五个子弹涵盖。

关于这种解释我可以说的是,在圣克鲁斯会议之前 ,我从来没有想过这种可能性 ,尽管我在很多年里已经深入思考了这些规则。 即使这个案例可能被现有的措辞所涵盖, 我也建议可能值得寻找一种不太透明的配方。

以下讨论和提出的解决方案很长,很难总结,但似乎最终删除了上述第五条,并通过调整6.5其他部分来解决问题。 但如上所述,所涉及的问题无法解决,我也没有看到后续提案。

因此,似乎标准的措辞确实似乎允许OP演示的情景,尽管我的理解是这是无意的,因此我会避免它,并且它可能在后来的标准中可能会改变为不符合。

我认为本文不适用:

一种聚合或联合类型 ,包括其成员中的上述类型之一(包括递归地,子聚合或包含联合的成员),

sp->x类型为unsigned int ,它不是聚合类型或联合类型。

在您的代码中没有严格的别名冲突:可以将unsigned int作为unsigned int读取。

结构可能对数组有不同的对齐要求,但除此之外没有问题。

通过“聚合或联合类型”访问将是:

 st = *sp; 

我承认,我可以用这种方式在一个局部定义的数组上放置一个struct的想法是坦率的异国情调。 我仍然认为C99和所有后续标准都允许。 事实上,成员作为对象本身就是6.7.5中的第一个要点是非常有争议的:

与对象的有效类型兼容的类型

我认为这是MM的观点。

从另一个角度看问题,让我们注意到,将成员sp->x作为一个对象的别名是绝对合法的(在一个严格符合的环境中)。

在我的OP中的代码的上下文中考虑一个带有原型void doit(int* ip,s* sp);的函数void doit(int* ip,s* sp); 以下调用预计会在逻辑上表现:

 doit(&(sp->x),sp); 

注意:程序逻辑可能(当然)可能不符合要求。 例如,如果doit递增sp->x直到它超过*ip那么就会出现问题! 但是,在一致的编译器中不允许的是,由于优化器忽略了混叠潜力,结果会被伪像破坏。

我认为,如果语言要求我编写代码,那么C会更弱一些:

 int temp=sp->x; doit(&temp,sp); sp->x=temp; 

想象一下所有必须对任何函数调用进行监管的情况,以便对传递的结构的任何部分进行潜在的别名访问。 这种语言可能无法使用。

显然,如果硬件优化(即非兼容)编译器不能识别ip可能是sp中间的成员的别名,那么它可能会生成doit()的完整哈希。 这与这次讨论无关。

列出编译器何时可以(并且不能)做出这样的假设的原因被理解为标准需要围绕别名设置非常精确的参数的原因。 这是为优化器提供一些可以忽略的条件。 在诸如“C”的低级语言中,可以合理地(甚至是期望的)说可以使用到可访问的有效位模式的适当对齐的指针来访问值。

绝对确定我的OP中的sp->x指向一个保持有效unsigned int的正确对齐位置。

智能问题在于编译器/优化器是否同意这是访问该位置的合法方式,或者可忽略为未定义的行为。

正如doit()示例所示,它绝对确定了一个结构可以被分解并被视为单独的对象,而这些对象恰好具有特殊的关系。

这个问题似乎是关于一组恰好具有这种特殊关系的成员可以拥有“奠定他们”的结构的情况。

我想大多数人会同意这个答案底部的程序执行有效的,有价值的function,如果与某些I / O库相关联,可以“抽象”读取和编写结构所需的大量工作。 您可能认为有更好的方法,但我不希望很多人认为这不是一种不合理的方法。

它完全按照这种方式运行 – 它按成员构建一个结构成员,然后通过该结构访问它。

我怀疑一些反对OP中代码的人对此更加放松。 首先,它在从免费商店分配的内存上运行,作为“未打字”的通用对齐存储。 其次,它构建了一个完整的结构。 在OP中,我指出了规则(至少看起来允许),你可以排列结构的位,只要你只取消引用那些位,一切都OK。

我有点赞同这种态度。 我认为OP略显不正常,语言在标准的一个写得不好的角落里延伸。 不适合穿衬衫。

但是,我绝对认为禁止下面的技术是错误的,因为它们排除了一种逻辑上非常有效的技术,可以识别结构可以从对象构建,就像分解成它们一样。

但是,我会说这样的事情是我唯一能想出这种方法似乎值得的东西。 但另一方面,如果你不能将数据分开和/或将它们组合在一起,那么你很快就会开始打破C结构的概念POD – 它们各部分的可能填充总和,仅此而已。

 #include  #include  #include  typedef enum { is_int, is_double //NB:TODO: support more types but this is a toy. } type_of; //This function allocates and 'builds' an array based on a provided set of types, offsets and sizes. //It's a stand-in for some function that (say) reads structures from a file and builds them according to a provided //recipe. int buildarray(void**array,const type_of* types,const size_t* offsets,size_t mems,size_t sz,size_t count){ const size_t asize=count*sz; char*const data=malloc(asize==0?1:asize); if(data==NULL){ return 1;//Allocation failure. } int input=1;//Dummy... const char*end=data+asize;//One past end. Make const for safety! for(char*curr=data;curr 

我认为这是一个有趣的紧张局势。 C旨在成为低级高级语言,并使程序员几乎可以直接访问机器操作和内存。 这意味着程序员可以满足硬件设备的任意需求并编写高效的代码。 然而,如果程序员被给予绝对控制,例如关于“如果它适合它就行”的方法,那么优化器就会破坏它的游戏。 所以奇怪的是,值得保持一点性能回来从优化器返回一个股息。

C99标准的第6.5节尝试(并没有完全成功)设置该边界。