C 中未命名的结构/联合有啥好处?
Posted
技术标签:
【中文标题】C 中未命名的结构/联合有啥好处?【英文标题】:What are the benefits of unnamed structs / unions in C?C 中未命名的结构/联合有什么好处? 【发布时间】:2012-11-02 19:28:47 【问题描述】:我发现一个代码实现为如下所示的类似演示..
struct st
int a;
struct
int b;
;
;
6.58 structs/unions
中未命名的 struct/union
字段
如ISO C11
允许。
但是它有什么好处呢?
因为无论如何我都可以以与
相同的方式访问数据成员int main()
struct st s;
s.a=11;
s.b=22;
return 0;
使用 , 在 gcc 4.5.2 上编译
gcc -Wall demo.c -o demo
没有错误,
【问题讨论】:
What are anonymous structs and unions useful for in C11? 的可能重复项 【参考方案1】:它不必是结构内的匿名结构,我觉得这不是很有用:这通常只会通过引入更多填充来稍微改变布局,没有其他可见效果(与内联子结构到父结构)。
我认为匿名结构/联合的优势在别处: 它们可用于将匿名结构放置在联合中或将匿名联合放置在结构中。
例子:
union u
int i;
struct char b1; char b2; char b3; char b4; ;
;
【讨论】:
你能解释一下如何使用这个联合吗?例如,如果我有 u 的实例 x 并使用x.b1='a'
,那么其余的 b2、b3、b4 是否会被初始化并占用内存空间?
@Herbert 与联合中的传统(命名)结构相同。您的问题实际上是关于包含结构的联合。你应该把它作为一个 SO 问题而不是作为对更具体问题的答案的评论,但由于你做了后者,x.b1='a'
不会初始化成员 b2
、b3
、b4
但这些执行“占用内存空间”,通过打印sizeof (union u)
的值可以看出。理论上,如果你声明了一个 union u
变量,而你只使用了 b1
成员,一个足够聪明的编译器可能只会为 b1
保留内存,...
@Herbert 但原则上声明union u
对象意味着您可能希望稍后写入它包含的结构的任何成员,因此应为它们保留内存。【参考方案2】:
好处很明显,不是吗?它使程序员免于想出一个名字!从naming things is hard 开始,如果没有真正需要,可以避免这样做,这很好。
这也是一个非常明确的信号,表明这个struct
是本地的并且从未在其他任何地方使用,但在作为父结构中的字段的上下文中,这是非常非常好的信息,因为它减少了 可能性 不必要的耦合。
把它想象成static
;它将内部struct
的可见性限制在外部struct
的可见性类似于(但当然不等同于)static
如何将全局符号的可见性限制在它们出现的编译单元中。
【讨论】:
小细节 .. 静态 ??还有一个,如果那个是本地的,那么使用相同的标识符名称会出错..但是在函数中,我们可以在 内给出相同的名称标识符,因为块作用域为什么这里不允许 structstruct
变得如此本地化,以至于很难理解程序员为什么不将其成员直接内联在父结构中。当前版本中的此答案未列出与该替代方案有关的任何好处。与内联成员相比,嵌套结构之间的布局不同(具有更多填充,这可能是预期的效果,但通常被认为是不利的)。
由于上述原因,匿名union
似乎比匿名结构更有利。【参考方案3】:
我刚刚遇到了匿名union
的巨大好处。但是请注意,这不是胆小的人的故事,也不是推荐的做法。
注意:另见Anonymous union within struct not in c99?
在一个包含数百个源代码文件的旧 C 程序中,有一个全局变量 struct
,其中包含一个 struct
作为成员。所以全局变量的类型定义看起来像这样:
typedef struct
LONG lAmount;
STRUCTONE largeStruct; // memory area actually used for several different struct objects
ULONG ulFlags;
STRUCTCOMMON;
struct
,STRUCTONE,是几个大型结构之一,但在编写此代码时,其他结构都小于 STRUCTONE。所以这个内存区域,largeStruct
被用作union
,但没有正确的源语句表明这一点。而是使用memcpy()
将各种struct
变量复制到该区域。更糟糕的是,有时这是通过全局变量的实际名称,有时是通过指向全局变量的指针。
随着时间的推移,最近的变化通常会导致其他结构之一成为最大的。而且我不得不浏览一百个文件,以查找这些文件的使用位置以及所有各种别名和其他所有内容。
然后我想起了匿名工会。所以我将typedef
修改为如下:
typedef struct
LONG lAmount;
union
// anonymous union to allow for allocation of largest space needed
STRUCTONE largeStruct; // memory area actually used for several different struct objects
STRUCTTHREE largerStruct; // memory area for even larger struct
;
ULONG ulFlags;
STRUCTCOMMON;
然后重新编译所有东西。
所以现在我不再需要那些我不高兴地期待的源代码审查和回归测试了。
我现在可以开始使用这个全局变量来慢慢修改源代码,以便在我自己的时间表上使这个源代码达到更现代的标准。
附录 - 匿名 struct
内匿名 union
在同一个源代码体中工作时,我遇到了一个使用这种技术的应用程序,它带有一个二进制记录,其中可能包含来自几个不同结构之一的日期,这些结构应该是相同的长度。我发现的问题是由于程序员错误,一个结构的大小与其他结构不同。
作为纠正这个问题的一部分,我想要一个解决方案,让编译器能够计算出数据结构的正确大小。
由于这些结构在结构的几个成员中包含一些差异,并添加了填充变量以使它们都具有相同的大小,因此我选择了匿名联合,除了其中一个结构之外,它工作正常。
我发现我可以添加一个匿名结构作为联合的一部分,这样只要联合的各个成员和添加的匿名结构具有不同的名称,它就可以在 Visual Studio 2015 中正常编译。
重要提示:此解决方案需要 #pragma pack(1)
和 Visual Studio 2015 来打包字节边界上的结构和联合。如果不使用pragma
,编译器可能会在各种结构和联合中引入未知填充。
为了标准化匿名union
和匿名struct
,我创建了以下define
。
#define PROGRPT_UNION_STRUCT \
union \
SHORT sOperand1; /* operand 1 (SHORT) */ \
LONG lOperand1; /* operand 1 (LONG) */ \
PROGRPT_ITEM Operand1; /* operand 1 */ \
struct \
UCHAR uchReserved3; /* */ \
USHORT usLoopEnd; /* offset for loop end */ \
UCHAR uchReserved4; /* */ \
; \
;
然后在这个示例中使用它,其中三个结构用于访问从文件读取的数据记录中的二进制数据。
/* loop record */
typedef struct
UCHAR uchOperation; /* operation code (LOOP) */
UCHAR uchRow; /* position (row) */
UCHAR uchLoopBrace; /* loop brace (begin/end) */
UCHAR uchReserved1; /* */
TCHAR auchReserved2[ 2 ]; /* */
UCHAR uchCondition; /* condition code */
PROGRPT_ITEM LoopItem; /* loop record */
PROGRPT_UNION_STRUCT
PROGRPT_ITEM Reserved5; /* */
PROGRPT_LOOPREC;
/* print record */
typedef struct
UCHAR uchOperation; /* operation code (PRINT) */
UCHAR uchRow; /* position (row) */
UCHAR uchColumn; /* position (column) */
UCHAR uchMaxColumn; /* max no of column */
TCHAR auchFormat[ 2 ]; /* print format/style */
UCHAR uchCondition; /* condition code */
PROGRPT_ITEM PrintItem; /* print item */
PROGRPT_UNION_STRUCT
PROGRPT_ITEM Operand2; /* ope2 for condition */
PROGRPT_PRINTREC;
/* mathematics record ( accumulator.total = LONG (+,-,*,/) opr2) */
typedef struct
UCHAR uchOperation; /* operation code (MATH) */
UCHAR uchRow; /* position (row) */
UCHAR uchColumn; /* position (column) */
UCHAR uchMaxColumn; /* max no of column */
TCHAR auchFormat[ 2 ]; /* format style */
UCHAR uchCondition; /* condition code */
PROGRPT_ITEM Accumulator; /* accumulator */
PROGRPT_UNION_STRUCT
PROGRPT_ITEM Operand2; /* operand 2 */
PROGRPT_MATHTTL;
原来是
typedef struct
UCHAR uchOperation; /* operation code (LOOP) */
UCHAR uchRow; /* position (row) */
UCHAR uchLoopBrace; /* loop brace (begin/end) */
UCHAR uchReserved1; /* */
TCHAR auchReserved2[ 2 ]; /* */
UCHAR uchCondition; /* condition code */
PROGRPT_ITEM LoopItem; /* loop record */
UCHAR uchReserved3; /* */
USHORT usLoopEnd; /* offset for loop end */
UCHAR uchReserved4; /* */
PROGRPT_ITEM Reserved5; /* */
PROGRPT_LOOPREC;
/* print record */
typedef struct
UCHAR uchOperation; /* operation code (PRINT) */
UCHAR uchRow; /* position (row) */
UCHAR uchColumn; /* position (column) */
UCHAR uchMaxColumn; /* max no of column */
TCHAR auchFormat[ 2 ]; /* print format/style */
UCHAR uchCondition; /* condition code */
PROGRPT_ITEM PrintItem; /* print item */
PROGRPT_ITEM Operand1; /* ope1 for condition */
PROGRPT_ITEM Operand2; /* ope2 for condition */
PROGRPT_PRINTREC;
/* mathematics record ( accumulator.total = LONG (+,-,*,/) opr2) */
typedef struct
UCHAR uchOperation; /* operation code (MATH) */
UCHAR uchRow; /* position (row) */
UCHAR uchColumn; /* position (column) */
UCHAR uchMaxColumn; /* max no of column */
TCHAR auchFormat[ 2 ]; /* format style */
UCHAR uchCondition; /* condition code */
PROGRPT_ITEM Accumulator; /* accumulator */
LONG lOperand1; /* operand 1 (LONG) */
PROGRPT_ITEM Operand2; /* operand 2 */
PROGRPT_MATHTTL;
使用所有各种记录类型的union
,如下所示:
typedef union
PROGRPT_LOOPREC Loop; /* loop record */
PROGRPT_PRINTREC Print; /* print record */
PROGRPT_MATHOPE MathOpe; /* math (with operand) */
PROGRPT_MATHTTL MathTtl; /* math (with total) */
PROGRPT_MATHCO MathCo; /* math (with count) */
PROGRPT_RECORD;
这些记录格式用在类似于下面的代码中:
for ( usLoopIndex = 0; usLoopIndex < usMaxNoOfRec; )
ULONG ulActualRead = 0; /* actual length of read record function */
PROGRPT_RECORD auchRecord;
/* --- retrieve a formatted record --- */
ProgRpt_ReadFile( ulReadOffset, &auchRecord, PROGRPT_MAX_REC_LEN, &ulActualRead );
if ( ulActualRead != PROGRPT_MAX_REC_LEN )
return ( LDT_ERR_ADR );
/* --- analyze operation code of format record, and
store it to current row item buffer --- */
switch ( auchRecord.Loop.uchOperation )
case PROGRPT_OP_PRINT: /* print operation */
sRetCode = ProgRpt_FinPRINT( &ReportInfo, &auchRecord.Print, uchMinorClass, NULL );
break;
case PROGRPT_OP_MATH: /* mathematics operation */
sRetCode = ProgRpt_FinMATH(&auchRecord.MathOpe, NULL );
break;
case PROGRPT_OP_LOOP: /* loop (begin) operation */
ProgRpt_PrintLoopBegin( &ReportInfo, &auchRecord.Loop );
switch ( auchRecord.Loop.LoopItem.uchMajor )
case PROGRPT_INDKEY_TERMNO:
sRetCode = ProgRpt_IndLOOP( &ReportInfo, &auchRecord.Loop, uchMinorClass, usTerminalNo, ulReadOffset );
usLoopIndex += auchRecord.Loop.usLoopEnd;
ulReadOffset += ( PROGRPT_MAX_REC_LEN * auchRecord.Loop.usLoopEnd );
break;
default:
return ( LDT_ERR_ADR );
break;
default:
return ( LDT_ERR_ADR );
// .......
【讨论】:
【参考方案4】:我在开发将通过指针访问的连续地址结构时使用了匿名结构。更具体地说,我将在父结构中使用匿名结构来启用对内存的某些部分进行位字段处理,这些部分被划分为标记数据的较小部分。
注意编译器如何打包位域信息,位域结构的第一个成员可以是 LSB 或 MSB。
typedef struct
uint32_t a;
struct
uint32_t b : 16;
uint32_t c : 8;
uint32_t d : 7;
uint32_t e : 1;
;
Parent;
#define ADDRESS ((Parent*)(uint16_t)0xF0F0)
ADDRESS->a = data_32_bits;
ADDRESS->b = data_16_bits;
ADDRESS->c = data_8_bits;
ADDRESS->d = data_7_bits;
ADDRESS->e = data_1_bit;
【讨论】:
以上是关于C 中未命名的结构/联合有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章