GCC中混合C++11与C++20编译是否合规?结构体大小异常问题
混合C11与C20编译导致结构体大小不一致的问题
我在头文件中声明了结构体C,分别用-std=c++11和-std=c++20编译两个不同的编译单元,结果该结构体在两个单元中的sizeof值不同。请问这种混合不同C++标准版本编译代码的做法是否被允许?还是我的GCC使用方式有误?
补充信息:
- 使用的是同一版本GCC,未混用不同编译器版本;
- 代码中未使用不同标准下不兼容的特性;
- 仅使用了输出所需的标准库,问题并非源于此;
- 运行环境为Debian 11系统上的GCC 10.2.1 20210110(Debian 10.2.1-6,Buster版本),怀疑是该版本GCC存在bug。
问题复现代码
File 1.h
#ifndef DEF1 #define DEF1 #include "c.h" void f1(C* c); C* g1(); #endif
File 2.h
#ifndef DEF2 #define DEF2 #include "c.h" void f2 (C* c); C* g2(); #endif
File 1.cpp
#include "c.h" #include <stdio.h> void f1 (C* c) { printf("sizeof(C)=%ld\n", sizeof(C)); printf("i2=%d\n", c->i2); printf("&i2-&i1=%ld\n", ((char*)&c->i2) - ((char*)&c->i1)); } C* g1() { C* c = new C; c->i2 = 1; return c; }
File 2.cpp
#include "c.h" #include <stdio.h> void f2 (C* c) { printf("sizeof(C)=%ld\n", sizeof(C)); printf("i2=%d\n", c->i2); printf("&i2-&i1=%ld\n", ((char*)&c->i2) - ((char*)&c->i1)); } C* g2() { C* c = new C; c->i2 = 2; return c; }
File c.h
#ifndef CDEF #define CDEF #include <stdint.h> struct A { double d1{0.0}; double d2{0.0}; double d3{0.0}; double d4{0.0}; double d5{0.0}; double d6{0.0}; double d7{0.0}; int32_t i1{}; }; #ifdef DER struct C : public A { int32_t i2{}; }; #else struct C { double d1{0.0}; double d2{0.0}; double d3{0.0}; double d4{0.0}; double d5{0.0}; double d6{0.0}; double d7{0.0}; int32_t i1{}; int32_t i2{}; }; #endif #endif
File main.cpp
#include "c.h" #include "1.h" #include "2.h" int main() { f1(g2()); f2(g1()); }
File bar.sh
g++ -std=c++20 -DDER -c 2.cpp g++ -std=c++11 -DDER -c 1.cpp g++ -std=c++20 -DDER -c main.cpp g++ -std=c++20 2.o 1.o main.o -o prog ./prog
执行结果:
sizeof(C)=64 i2=0 &i2-&i1=4 sizeof(C)=72 i2=0 &i2-&i1=8
问题分析与解答
混合不同C++标准版本编译属于未定义行为
即使使用同一编译器的同一版本,跨编译单元混合不同C标准版本编译也是不安全的,不符合C标准的要求。因为不同C++标准可能会修改类型的内存布局规则,导致同一类型在不同编译单元中出现大小、成员偏移不一致的情况,进而引发内存访问错误,就像你遇到的i2值异常的问题。具体原因:C++20的尾填充复用特性
你的案例中,结构体大小差异的根源是C++20新增的派生类成员复用基类尾填充的规则:- 基类
A的内存布局:7个double(共56字节)+1个int32_t(4字节),总60字节。由于double的对齐要求是8字节,A会被填充到64字节(尾部有4字节的空闲填充空间)。 - 在C++20中,派生类
C的int32_t i2可以直接放入基类A的尾部填充空间,因此C的总大小保持为64字节。 - 在C++11中,派生类成员无法复用基类的尾填充空间,
i2必须放在A的后面,加上对齐要求(C的对齐标准与A一致为8字节),64+4=68字节会被填充到72字节。
- 基类
解决方案
所有参与链接的编译单元必须使用相同的C++标准版本编译,确保同一类型的内存布局在整个程序中完全一致。修改你的编译脚本,统一使用-std=c++11或-std=c++20编译所有.cpp文件即可解决问题。
内容的提问来源于stack exchange,提问作者SR_
相关产品推荐
相关产品推荐

