我们能在联合体中使用指针吗?

15

如果不行,为什么?联合在结构体上的用途是什么?

2个回答

14

你可以在联合体中使用任何数据类型,没有限制。

至于在结构体和联合体之间的选择,结构体按顺序在内存中布局其数据。这意味着它们的所有子组件是独立的。

而联合体则使用相同的内存来存储所有子组件,因此一次只能存在一个子组件。

例如:

                                 ┌─────┬─────┐
struct { int a; float b }  gives │  a  │  b  │
                                 └─────┴─────┘
                                    ▲     ▲
                                    │     │
                 memory location:  150   154
                                    │
                                    ▼
                                 ┌─────┐
union { int a; float b }  gives  │  a  │
                                 │  b  │
                                 └─────┘

结构体用于表示由其他对象组成的“对象”,比如一个点对象由两个整数组成,即x和y坐标:

typedef struct {
    int x;           // x and y are separate
    int y;
} tPoint;

联合体通常用于一个对象可以是多个东西中的一个,但每次只能是其中的一种情况,比如类型不确定的存储系统:

typedef enum { STR, INT } tType;
typedef struct {
    tType typ;          // typ is separate.
    union {
        int ival;       // ival and sval occupy same memory.
        char *sval;
    }
} tVal;

尽管如今(除了嵌入式系统等低级别工作)内存保存变得越来越不重要,但它们对于节省内存仍然非常有用,因此你不会看到很多这样的应用。


15
很抱歉,@Alexandre,我必须对几乎所有的内容持不同意见 :-) 尤其是在小型嵌入式系统中,它们(即联合体和位域)目前仍然非常有用,可以节省内存。如果您了解底层布局(包括实现定义的小问题),它们并不难使用。而且在有人将 Boost 移植到 C 之前(请查看标签),变体类型也不会有太大的用处 :-) - paxdiablo
1
@AlexandreC。标记联合有什么问题?你的支持论点似乎站不住脚,并且已经被很好地反驳了。你有任何进一步的辩解吗?我认为“限制”是无关紧要的,因为这个构造(标记联合)已经充分装备完成工作,“困难”显然是主观的。具有讽刺意味的是,我认为在等待boost时保持流畅比清晰地思考我正在编程的内容要困难得多,耗费更多时间。 - MickLH
我该如何访问此结构中的sval成员?是这样吗:struct tVal tval; tval->sval; 还是我们需要为联合指定标识符,并使用它来访问联合成员,例如 tval.uval->sval; - Kamal Pandey
@KamalPandey:根据这个定义,它不是一个指针,所以实际上应该是tval.sval。而且这只适用于存在一个未命名联合体的情况。如果你有像union { int ival; char * sval} name;这样的命名联合体,我认为你需要指定为:tval.name.sval - paxdiablo
@paxdiablo 感谢您的回复。那么对于解引用 sval 指针,应该是 *tval.sval 还是 tval.*sval - Kamal Pandey
显示剩余4条评论

4

根据ISO/IEC 9899:TC3(C99标准):

联合类型描述了一个重叠的非空成员对象集,每个对象都有可选的名称和可能不同的类型。

简而言之,联合成员的内存空间重叠,并且您为联合成员指定的名称允许您按大小在该位置读取内存。请考虑以下示例:

#include <stdio.h>
#include <stdint.h>

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} somenewtype;

typedef union
{
    uint32_t* p;
    uint8_t* q;
} somepointer;

int main(int argc, char** argv)
{
    uint32_t r;
    uint8_t s;
    somenewtype z;
    somepointer p;
    r = 0x11223344; s = 0x11;
    z.x = 0x11223344;
    p.p = &r;
    p.q = &s;
    printf("%x%x%x%x\n", z.d, z.c, z.b, z.a);
    printf("%x %x\n", *(p.p), *(p.q));
}

在第一个printf中,我们打印出32位整数的8位部分。当然希望在该匿名结构中没有填充项。
第二个printf中呢?我必须使用gdb进行步进以理解,但最终我明白了。
p.p = (uint32_t *) 0x7fffffffde5c;
p.q =  (uint8_t *) 0x7fffffffde5b "\021D3\"\021P\337\377\377\377\177";
p.p = (uint32_t *) 0x7fffffffde5b;

当然,指针的大小都是一样的,所以赋值p.q会覆盖p.p的地址。我强烈怀疑将32位整数的地址解引用为8位指针会打印出“该位置+32位大小的任何内容”,这巧合地对我来说恰好是22334411。但是我怀疑,在那一点上,行为是未定义的。
无论如何,这个小练习的重点是向你展示:
  • 联合可以用于通过不同的“类型”修饰符访问相同的内存位置。
  • 您必须小心处理并了解您正在使用的基础类型。如果要使用指针,请注意它们的修改,因为您可能开始指向谁知道什么。
我应该指出,我可以看到somenewtype的实际用途,但不能看到somepointer的实际用途——那是一个我非常确定会出问题的虚构例子。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接