gcc / clang将派生结构的字段布置在基本结构的后填充中 [英] gcc/clang lay out fields of a derived struct in the back-padding of base struct

查看:252
本文介绍了gcc / clang将派生结构的字段布置在基本结构的后填充中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我涉及到填充和继承时,我对gcc和clang如何构造结构感到困惑。以下是一个示例程序:

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

struct A
{
void * m_a;
};

struct B:A
{
void * m_b1;
char m_b2;
};

struct B2
{
void * m_a;
void * m_b1;
char m_b2;
};

struct C:B
{
short m_c;
};

struct C2:B2
{
short m_c;
};

int main()
{
C c;
memset(& c,0,sizeof(C));
memset((B *)& c,-1,sizeof(B));

printf(
c.m_c =%d; sizeof(A)=%d sizeof(B)=%d sizeof(C)=%d\\\

c.m_c,sizeof(A),sizeof(B),sizeof(C)
);

C2 c2;
memset(& c2,0,sizeof(C2));
memset((B2 *)& c2,-1,sizeof(B2));

printf(
c2.m_c =%d; sizeof(A)=%d sizeof(B2)=%d sizeof(C2)=%d\\\

c2.m_c,sizeof(A),sizeof(B2),sizeof(C2)
);

return 0;
}

输出:

  $ ./a.out 
c.m_c = -1; sizeof(A)= 8 sizeof(B)= 24 sizeof(C)= 24
c2.m_c = 0; sizeof(A)= 8 sizeof(B2)= 24 sizeof(C2)= 32

C2布局不同。在C1中,m_c被分配在结构B1的后填充中,因此被第二memset()覆盖;与C2不会发生。



使用的编译器:

  $ clang --version 
Ubuntu clang版本3.3-16ubuntu1(branches / release_33)(基于LLVM 3.3)
目标:x86_64-pc-linux-gnu
线程模型:posix

$ c ++ - -version
c ++(Ubuntu 4.8.2-19ubuntu1)4.8.2
版权所有(C)2013自由软件基金会,
这是免费软件;请参阅复制条件的来源。有NO
保修;甚至不适用于适销性或特定用途的适用性。

使用-m32选项也会发生同样的情况(显然,输出中的大小会有所不同) p>

Microsoft Visual Studio 2010 C ++编译器的x86和x86_64版本没有这个问题(即他们同样布置了结构体С1和C2)



如果它不是一个错误,并且是设计,那么我的问题是:


  1. 分配或不分配派生结构
    的字段(例如为什么它不会发生在C2?)

  2. 有任何方法来覆盖这个行为


    $ b


    $ b

    Vladimir

    解决方案

    不同之处在于,编译器允许使用上一个对象的填充已经是不只是数据,并且不支持 memcpy



    B 结构不仅仅是数据,因为它是一个派生对象,因此可以使用它的松散空间,因为如果你 memcpy



    B2 / code>而是只是一个结构和向后兼容性要求其大小(包括松弛空间)只是内存您的代码允许使用 memcpy


    I'm confused with how gcc and clang lay out structs when both padding and inheritance are involved. Here's a sample program:

    #include <string.h>
    #include <stdio.h>
    
    struct A
    {
        void* m_a;
    };
    
    struct B: A
    {
        void* m_b1;
        char m_b2;
    };
    
    struct B2
    {
        void* m_a;
        void* m_b1;
        char m_b2;
    };
    
    struct C: B
    {
        short m_c;
    };
    
    struct C2: B2
    {
        short m_c;
    };
    
    int main ()
    {
        C c;
        memset (&c, 0, sizeof (C));
        memset ((B*) &c, -1, sizeof (B));
    
        printf (
            "c.m_c = %d; sizeof (A) = %d sizeof (B) = %d sizeof (C) = %d\n", 
            c.m_c, sizeof (A), sizeof (B), sizeof (C)
            );
    
        C2 c2;
        memset (&c2, 0, sizeof (C2));
        memset ((B2*) &c2, -1, sizeof (B2));
    
        printf (
            "c2.m_c = %d; sizeof (A) = %d sizeof (B2) = %d sizeof (C2) = %d\n", 
            c2.m_c, sizeof (A), sizeof (B2), sizeof (C2)
            );
    
        return 0;
    }
    

    Output:

    $ ./a.out
    c.m_c = -1; sizeof (A) = 8 sizeof (B) = 24 sizeof (C) = 24
    c2.m_c = 0; sizeof (A) = 8 sizeof (B2) = 24 sizeof (C2) = 32
    

    Structs C1 and C2 are laid out differently. In C1 m_c is allocated in the back-padding of struct B1 and is therefore overwritten by the 2nd memset (); with C2 it doesn't happen.

    Compilers used:

    $ clang --version
    Ubuntu clang version 3.3-16ubuntu1 (branches/release_33) (based on LLVM 3.3)
    Target: x86_64-pc-linux-gnu
    Thread model: posix
    
    $ c++ --version
    c++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
    Copyright (C) 2013 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    

    The same happens with -m32 option (sizes in the output will be different, obviously).

    Both x86 and x86_64 versions of Microsoft Visual Studio 2010 C++ compiler don't have this issue (i.e. they lay out structs С1 and C2 identically)

    If it's not a bug and is by design, then my questions are:

    1. what are the precise rules for allocating or not allocating fields of a derived struct in the back-padding (e.g. why it doesn't happen with C2?)
    2. is there any way to override this behaviour with switches/attributes (i.e. lay out just like MSVC does)?

    Thanks in advance.

    Vladimir

    解决方案

    The difference is that the compiler is allowed to use the padding of a previous object if that object is already "not just data" and manipulating it say with memcpy is not supported.

    The B structure is not just data, because it's a derived object and therefore the slack space of it can be used because if you're memcpy-ing a B instance around you're already violating the contract.

    B2 instead is just a structure and backward compatibility requires that its size (including the slack space) is just memory your code is allowed to play with using memcpy.

    这篇关于gcc / clang将派生结构的字段布置在基本结构的后填充中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆