内存布局-C并集

内存布局-C并集,c,memory,C,Memory,我有一个联合类型的数组,包含三个整数,每个4字节,一个浮点4字节,一个双8字节和一个字符1字节 如果我将0x3131分配给三个整数元素中的每一个,然后打印并集的字符,我将得到数字1。为什么? 我不理解输出,我知道3 0x3131的位是 0011000110001001100010011000100110001001100010011000100110001001100010011000100110001001100010011000100110001因为“1”==0x31。您正在将其打印为字符,

我有一个联合类型的数组,包含三个整数,每个4字节,一个浮点4字节,一个双8字节和一个字符1字节

如果我将0x3131分配给三个整数元素中的每一个,然后打印并集的字符,我将得到数字1。为什么?

我不理解输出,我知道3 0x3131的位是
0011000110001001100010011000100110001001100010011000100110001001100010011000100110001001100010011000100110001

因为“1”==0x31。您正在将其打印为字符,而不是整数


因为它是一个并集,所有int和char共享同一个内存位置,所以float和double在此上下文中并不重要。因此,将0x3131赋值给int确实会影响char值-这没有什么令人困惑的。

因为“1”==0x31。您正在将其打印为字符,而不是整数


因为它是一个并集,所有int和char共享同一个内存位置,所以float和double在此上下文中并不重要。因此,将0x3131赋值给int确实会影响char值——这没有什么让人困惑的地方。

联合的每个成员都有相同的起始地址;不同的成员可能有不同的大小。工会作为一个整体的规模至少是任何成员的最大规模;端部可能有额外的衬垫,以满足对齐要求

将值0x3131存储在union对象的前三个整数大小的内存区域中。0x3131是4个字节,每个字节的值为0x31

然后通过访问字符成员从偏移量0读取第一个字节。该字节的值为0x31,正好是ASCII和类似字符集中字符“1”的编码。如果你在基于EBCDIC的系统上运行你的程序,你会看到不同的结果

由于您没有向我们展示任何实际的源代码,我将根据您的描述:

#include <stdio.h>
#include <string.h>

void hex_dump(char *name, void *base, size_t size) {
    unsigned char *arr = base;
    char c = ' ';

    printf("%-8s : ", name);
    for (size_t i = 0; i < size; i ++) {
        printf("%02x", arr[i]);
        if (i < size - 1) {
            putchar(' ');
        }
        else {
            putchar('\n');
        }
    }
}

int main(void) {
    union u {
        int arr[3];
        float f;
        double d;
        char c;
    };

    union u obj;

    memset(&obj, 0xff, sizeof obj);

    obj.arr[0] = 0x31323334;
    obj.arr[1] = 0x35363738;
    obj.arr[2] = 0x393a3b3c;

    hex_dump("obj",     &obj,     sizeof obj);
    hex_dump("obj.arr", &obj.arr, sizeof obj.arr);
    hex_dump("obj.f",   &obj.f,   sizeof obj.f);
    hex_dump("obj.d",   &obj.d,   sizeof obj.d);
    hex_dump("obj.c",   &obj.c,   sizeof obj.c);

    printf("obj.c = %d = 0x%x = '%c'\n",
           (int)obj.c, (unsigned)obj.c, obj.c);

    return 0;
}
如您所见,每个成员的初始字节彼此一致,因为它们存储在相同的位置。尾部ff字节不受arr赋值的影响,这不是唯一有效的行为;标准上说他们采用了未指定的值。因为系统是小端,所以每个int值的高阶字节存储在内存中的最低位置

big-endian系统的输出为:

obj      : 31 32 33 34 35 36 37 38 39 3a 3b 3c ff ff ff ff
obj.arr  : 31 32 33 34 35 36 37 38 39 3a 3b 3c
obj.f    : 31 32 33 34
obj.d    : 31 32 33 34 35 36 37 38
obj.c    : 31
obj.c = 49 = 0x31 = '1'
如您所见,每个int的高阶字节位于内存中的最低位置

在所有情况下,obj.c的值都是obj.arr[0]的第一个字节,这将是高阶字节或低阶字节,具体取决于endianness


这在不同的系统中有很多不同的方式。int、float和double的大小可以不同。浮点数的表示方式可能会有所不同,尽管本例没有说明这一点。即使字节中的位数也可能不同;它至少有8个,但可以更大。在你可能遇到的任何系统上,它正好是8。标准允许在整数表示中填充位;在我所展示的例子中没有任何一个。

工会的每个成员都有相同的起始地址;不同的成员可能有不同的大小。工会作为一个整体的规模至少是任何成员的最大规模;端部可能有额外的衬垫,以满足对齐要求

将值0x3131存储在union对象的前三个整数大小的内存区域中。0x3131是4个字节,每个字节的值为0x31

然后通过访问字符成员从偏移量0读取第一个字节。该字节的值为0x31,正好是ASCII和类似字符集中字符“1”的编码。如果你在基于EBCDIC的系统上运行你的程序,你会看到不同的结果

由于您没有向我们展示任何实际的源代码,我将根据您的描述:

#include <stdio.h>
#include <string.h>

void hex_dump(char *name, void *base, size_t size) {
    unsigned char *arr = base;
    char c = ' ';

    printf("%-8s : ", name);
    for (size_t i = 0; i < size; i ++) {
        printf("%02x", arr[i]);
        if (i < size - 1) {
            putchar(' ');
        }
        else {
            putchar('\n');
        }
    }
}

int main(void) {
    union u {
        int arr[3];
        float f;
        double d;
        char c;
    };

    union u obj;

    memset(&obj, 0xff, sizeof obj);

    obj.arr[0] = 0x31323334;
    obj.arr[1] = 0x35363738;
    obj.arr[2] = 0x393a3b3c;

    hex_dump("obj",     &obj,     sizeof obj);
    hex_dump("obj.arr", &obj.arr, sizeof obj.arr);
    hex_dump("obj.f",   &obj.f,   sizeof obj.f);
    hex_dump("obj.d",   &obj.d,   sizeof obj.d);
    hex_dump("obj.c",   &obj.c,   sizeof obj.c);

    printf("obj.c = %d = 0x%x = '%c'\n",
           (int)obj.c, (unsigned)obj.c, obj.c);

    return 0;
}
如您所见,每个成员的初始字节彼此一致,因为它们存储在相同的位置。尾部ff字节不受arr赋值的影响,这不是唯一有效的行为;标准上说他们采用了未指定的值。因为系统是小端,所以每个int值的高阶字节存储在内存中的最低位置

big-endian系统的输出为:

obj      : 31 32 33 34 35 36 37 38 39 3a 3b 3c ff ff ff ff
obj.arr  : 31 32 33 34 35 36 37 38 39 3a 3b 3c
obj.f    : 31 32 33 34
obj.d    : 31 32 33 34 35 36 37 38
obj.c    : 31
obj.c = 49 = 0x31 = '1'
如您所见,每个int的高阶字节位于内存中的最低位置

在所有情况下,obj.c的值都是obj.arr[0]的第一个字节,这将是高阶字节或低阶字节,具体取决于endianness


这在不同的系统中有很多不同的方式。int、float和double的大小可以不同。浮点数的表示方式可能会有所不同,尽管本例没有说明这一点。即使字节中的位数也可能不同;它至少有8个,但可以更大。在你可能遇到的任何系统上,它正好是8。标准允许在整数表示中填充位;我所展示的例子中没有一个。

因为你很幸运。恶魔库

我已经从你的鼻子里飞出来了。你很熟悉你的代码,但我们不熟悉。请张贴代码,并请告诉我们什么编译器和平台操作系统+processor@cHao:根据描述,可能没有未定义的行为。写入一个并集的一个元素并读取另一个元素会将这些位重新解释为适当类型的表示。我认为它在C的一些早期版本中是未定义的。@Keith:它仍然是未定义的,AFAIK。任何事情都可能发生,即使它看起来起作用。@cHao:C11标准最新草案N1570第6.5.2.3节中的脚注说:如果用于读取联合对象内容的成员与上次用于在对象中存储值的成员不同,价值的对象表示的适当部分被重新解释为新类型的对象表示,如6.2.6所述,这一过程有时被称为“类型双关”。这可能是一个陷阱表示。C99有相同的脚注。第6.3.2.3节中的C90标准明确指出行为是实现定义的,而不是未定义的。因为你很幸运。恶魔可能会从你的鼻子里飞出来。你非常熟悉你的代码,但我们不是。请张贴代码,并请告诉我们什么编译器和平台操作系统+processor@cHao:根据描述,可能没有未定义的行为。写入一个并集的一个元素并读取另一个元素会将这些位重新解释为适当类型的表示。我认为它在C的一些早期版本中是未定义的。@Keith:它仍然是未定义的,AFAIK。任何事情都可能发生,即使它看起来起作用。@cHao:C11标准最新草案N1570第6.5.2.3节中的脚注说:如果用于读取联合对象内容的成员与上次用于在对象中存储值的成员不同,价值的对象表示的适当部分被重新解释为新类型的对象表示,如6.2.6所述,这一过程有时被称为“类型双关”。这可能是一个陷阱表示。C99有相同的脚注。第6.3.2.3节中的C90标准明确表示行为是实现定义的,而不是未定义的;查看证据:-原作者可能想使用格式化程序%02x打印F?谢谢,但我认为内存中的布局将与数组中的96位类似。然后,浮点数为32位,双精度为64位,字符为8位。数组仅占96位。@nullException,请参见Soren的编辑:它是并集-因此所有字段共享相同的96字节。谢谢,我现在就知道了!机器是little endian,这就是它读取最后4位的原因。@nullException:不,它不是读取最后4位,而是读取前8位。Endianness会影响前8位是高阶位还是低阶位,但由于存储了0x3131,因此无论哪种方式都会得到相同的结果。联合的所有成员从联合对象的同一内存位置偏移量0开始;它们可能有不同的尺寸。再说一次,如果你发布实际的代码而不是描述它,讨论这个问题会更容易;查看证据:-原作者可能想使用格式化程序%02x打印F?谢谢,但我认为内存中的布局将与数组中的96位类似。然后,浮点数为32位,双精度为64位,字符为8位。数组仅占96位。@nullException,请参见Soren的编辑:它是并集-因此所有字段共享相同的96字节。谢谢,我现在就知道了!机器是little endian,这就是它读取最后4位的原因。@nullException:不,它不是读取最后4位,而是读取前8位。Endianness会影响前8位是高阶位还是低阶位,但由于存储了0x3131,因此无论哪种方式都会得到相同的结果。联合的所有成员从联合对象的同一内存位置偏移量0开始;它们可能有不同的尺寸。同样,如果您发布实际的代码而不是描述它,那么讨论这个问题会更容易。