我承认,以这种方式将
struct
覆盖在本地定义的数组上的想法实际上是异国情调的。我仍然坚信C99和所有后续标准都允许这样做。事实上,很有争议的是,作为对象的成员本身在6.7.5的第一条要点中允许它:
与对象的有效类型兼容的类型
我认为这就是M.M的观点。从另一个角度看问题,让我们注意到(在一个严格符合环境中)将成员
sp->x
作为自己的对象别名是完全合法的。在我的OP代码的上下文中,考虑一个带有原型
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()
示例所显示的那样,结构可以被拆分并作为仅具有特殊关系的个体对象进行处理。
这个问题似乎是关于在一组具有特殊关系的成员中何时可以“覆盖”结构的情况。
我认为大多数人都会同意本答案底部的程序执行有效的、有价值的功能,如果与某些I/O库相关联,可以“抽象”大量读写结构所需的工作。
你可能认为有更好的方法来做这件事,但我不指望很多人认为这不是一个不合理的方法。
它通过确切的方式运作——逐个成员地构建结构,然后通过该结构访问它。
我怀疑一些反对OP代码的人会更放松一些。首先,它在从自由存储器中分配的内存上操作,作为“未类型化”的通用对齐存储。其次,它构建了一个完整的结构。在OP中,我指出规则(至少表面上允许)你可以排列结构的位,只要你只引用这些位,一切就没问题。
我有点赞同这种态度。我认为OP有点扭曲,语言拉伸在标准的糟糕写作角落里。不是值得信赖的事情。
然而,我绝对认为禁止以下技术是错误的,因为它们排除了一种逻辑上非常有效的技术,即识别结构可以像分解结构一样由对象构建。
然而,我要说的是,只有这样的方法才是我能想出来的,这种方法似乎值得尝试。但是另一方面,如果您不能将数据分解和/或组合在一起,那么您很快就会打破C结构是POD的概念-可能填充其部分的总和,仅此而已。
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
typedef enum {
is_int, is_double
} type_of;
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;
}
int input=1;
const char*end=data+asize;
for(char*curr=data;curr<end;curr+=sz){
for(size_t i=0;i<mems;++i){
char*mem=curr+offsets[i];
switch(types[i]){
case is_int:
*((int*)mem)=input++;
break;
case is_double:
*((double*)mem)=((double)input)+((double)input)/10.0;
++input;
break;
default:
free(data);
return 2;
}
}
}
if(array!=NULL){
*array=data;
}else{
free(data);
}
return 0;
}
typedef struct {
int a;
int b;
double c;
} S;
int main(void) {
const type_of types[]={is_int,is_int,is_double};
const size_t offsets[]={offsetof(S,a),offsetof(S,b),offsetof(S,c)};
S* array=NULL;
const size_t size=4;
int err=buildarray((void **)&array,types,offsets,3,sizeof(S),size);
if(err!=0){
return EXIT_FAILURE;
}
for(size_t i=0;i<size;++i){
printf("%zu: %d %d %f\n",i,array[i].a,array[i].b,array[i].c);
}
free(array);
return EXIT_SUCCESS;
}
我认为这是一种有趣的张力。C语言旨在成为一种低级高级语言,几乎直接让程序员访问机器操作和内存。这意味着程序员可以满足硬件设备的任意需求并编写高效的代码。然而,如果程序员被给予绝对控制,比如我的观点关于别名的“如果适合就可以”的方法,那么优化器的游戏就会受到破坏。所以奇怪的是,值得稍微牺牲一点性能,以便从优化器中获得回报。C99标准的第6.5节试图(并没有完全成功地)规定了这个边界。
gcc
和-fstrict-aliasing
。gcc
文档表示所有级别都存在各种程度的误报和漏报,因此不能真正作为代码是否违反严格别名规则的可靠指标。这些检查在许多简单的示例上都会失败。 - Shafik YaghmourX *
和Y *
(不兼容)指向重叠的内存位置,则它们都不能用于访问任何子对象。然而,从标准选择的措辞来看,如果X
和Y
都包含相同类型的成员,则通过X
和Y
访问该成员不是别名违规。 - M.M