【问题标题】:Code which was built with g++7, crashes with access to unaligned memory使用 g++7 构建的代码因访问未对齐的内存而崩溃
【发布时间】:2019-03-24 11:48:58
【问题描述】:

当我尝试运行使用 g++ 和优化 -O2 构建的程序时,这是一种奇怪的行为。 使用:

  • g++-7 (Ubuntu 7.3.0-16ubuntu3) 7.3.0
  • 内核 4.15.0-36-generic

我有两个成员的结构:

struct A {                                   
    uint8_t m8;                              
    Int128 m128;                                          
};                                           

Int128 在哪里:

// #pragma pack(push, 1)               
struct Base128 {
    __int128_t v{0};        
};              
// #pragma pack(pop)  

#pragma pack(push, 1)               
struct Int128: Base128 {            
    Int128();                       
};                                   
#pragma pack(pop)  

Base128 显式未打包,但 Int128 与对齐 1 打包 请注意,Base128 具有显式成员初始化,Int128 在另一个翻译单元具有手动定义的空主体构造函数(用于避免内联)。

当我将Base128 包装更改为与Int128 相同时,程序不会崩溃。

似乎编译器生成了无效指令:MOVAPS 而不是MOVUPS 用于访问构造函数中的__int128_t 成员:

00000000000006b0 <_ZN6Int128C1Ev>:
 6b0:    66 0f ef c0              pxor   %xmm0,%xmm0
 6b4:    55                       push   %rbp
 6b5:    48 89 e5                 mov    %rsp,%rbp
 6b8:    0f 29 07                 movaps %xmm0,(%rdi)
 6bb:    5d                       pop    %rbp
 6bc:    c3                       retq   
 6bd:    0f 1f 00                 nopl   (%rax)

反之亦然:

00000000000006b0 <_ZN6Int128C1Ev>:
 6b0:    66 0f ef c0              pxor   %xmm0,%xmm0
 6b4:    55                       push   %rbp
 6b5:    48 89 e5                 mov    %rsp,%rbp
 6b8:    0f 11 07                 movups %xmm0,(%rdi)
 6bb:    5d                       pop    %rbp
 6bc:    c3                       retq   
 6bd:    0f 1f 00                 nopl   (%rax)

你有什么想法:我做错了什么?

源代码:

test.h:

#pragma once

#include <cstdint>

//#pragma pack(push, 1) // it fixes problem
struct Base128 {
    __int128_t v{0};
};
//#pragma pack(pop)

#pragma pack(push, 1)
struct Int128: Base128 {
    Int128();
}; 
#pragma pack(pop)

struct A {
    uint8_t m8; 
    //Int128 __attribute__((aligned(16))) m128; // it fixes problem
    Int128 m128;    
};

test.cpp:

#include "test.h"

// Int128::Int128() : Base128{0} {} // Fixes (why ?!)
Int128::Int128() {}

main.cpp:

#include "test.h"
int main() {
    A a;
    return 0;
}

构建和运行:

g++-7 --save-temps -Wall -Wextra -std=c++14 -O2 -g main.cpp test.cpp && ./a.out

Source code on gitlab is here。它可以如下构建和运行:

./build.sh # build and run (crashes)
./build.sh [1..5] # where 1..5 -- different fixes

【问题讨论】:

  • 我没有查看详细信息,但#pragma pack(push, 1) 看起来很可疑。请在问题中提供minimal reproducible example(强调最小),而不是通过外部链接。
  • 您能否编辑掉代码中的#ifdefs,以便我们只看到失败的测试版本?这很难按原样遵循。
  • 谢谢你,我已经修正了你的笔记
  • 我的直觉告诉我这是一个编译器错误,但我必须说,从对齐的结构中继承明确未对齐的结构有点导致灾难,如果在这两种类型的指针之间如何执行强制转换不存在根本不可调和的冲突,我也不会感到惊讶。也许错误是首先编译成功...
  • 是的...考虑以下内容:gcc.godbolt.org/z/ZV_Ya1foo() 显然是按照应有的方式实现的,但是 bar() 会以与您正在经历的完全相同的方式导致灾难。

标签: c++ gcc segmentation-fault g++ memory-alignment


【解决方案1】:

来自alignasdocumentation,据我所知相当于gcc的打包属性:

如果声明中最严格(最大)的对齐方式比没有任何 alignas 说明符时的对齐方式弱(即,弱于其自然对齐方式或弱于同一对象或类型的另一个声明上的对齐方式),则程序格式错误:

struct alignas(8) S {};
struct alignas(1) U { S s; }; // error: alignment of U would have been 8 without alignas(1)

这个例子实际上和你的完全一样(把一个类的父级作为它的第一个成员)。

所以我们可以肯定地说你的程序格式不正确,因此调用了未定义的行为。您的大多数解决方法都可以被视为基本上只是“运气”。无需解释它们为何起作用。

有趣的是,在你的代码中换入标准的alignas() 时,gcc 仍然没有报错,但是 clang 开始正确地报告错误:https://gcc.godbolt.org/z/EEErXg

编辑:关于alignas()和gcc的打包等价的参考:

GCC 说它是 MSVC 功能的直接端口:https://gcc.gnu.org/onlinedocs/gcc-4.8.0/gcc/Structure_002dPacking-Pragmas.html

而微软说alignas() 是一回事:https://msdn.microsoft.com/en-us/library/2e70t5y1.aspx

【讨论】:

  • 有趣的是,在制作 Base128 alignas(1) clang 时会导致相同的错误。似乎无法使用小于 128_t (16) 的大小的值声明 alignas。但是当使用 __attribute__((packed)) 就可以了
猜你喜欢
  • 1970-01-01
  • 2019-08-11
  • 2012-02-22
  • 1970-01-01
  • 2013-10-24
  • 2010-11-07
  • 2013-10-21
  • 1970-01-01
相关资源
最近更新 更多