C++ 堆上已分配内存的总线错误

C++ 堆上已分配内存的总线错误,c++,solaris,bus-error,C++,Solaris,Bus Error,在这样的代码中,我有总线错误: char* mem_original; int int_var = 987411; mem_original = new char [250]; memcpy(&mem_original[250-sizeof(int)], &int_var, sizeof(int)); ... const unsigned char* mem_u_const = (unsigned char*)mem_original; ... const unsigned ch

在这样的代码中,我有总线错误

char* mem_original;
int int_var = 987411;
mem_original = new char [250];
memcpy(&mem_original[250-sizeof(int)], &int_var, sizeof(int));
...
const unsigned char* mem_u_const = (unsigned char*)mem_original;
...
const unsigned char *location = mem_u_const + 250 - sizeof(int);

std::cout << "sizeof(int) = " << sizeof(int) << std::endl;//it's printed out as 4
std::cout << "byte 0 = " << int(*location) << std::endl;
std::cout << "byte 1 = " << int(*(location+1)) << std::endl;
std::cout << "byte 2 = " << int(*(location+2)) << std::endl;
std::cout << "byte 3 = " << int(*(location+3)) << std::endl;
int original_var = *((const int*)location);
std::cout << "original_var = " << original_var << std::endl;
然后它失败了:

sizeof(int) = 4
byte 0 = 0
byte 1 = 15
byte 2 = 17
byte 3 = 19
Bus Error
它是在Solaris操作系统(C++5.12)上构建和运行的 同样的代码在Linux(GCC4.12)和Windows(msvc-9.0)上运行良好

我们可以看到:

  • 新[]在堆上分配了内存
  • 内存是可访问的(我们可以逐字节读取)
  • 内存正好包含应该包含的内容,而不是损坏的内容
  • 那么,总线错误的原因可能是什么呢?我应该去哪里看

    UPD:
    如果我把(…)
    location
    放在
    original\u var
    的末尾,它就可以工作了。但是
    *((const int*)位置的问题是什么?

    编译代码时,我收到以下警告:

    main.cpp:19:26: warning: cast from 'const unsigned char *' to 'const int *' increases required alignment from 1 to 4 [-Wcast-align]
        int original_var = *((const int*)location);
                             ^~~~~~~~~~~~~~~~~~~~
    

    这似乎是总线错误的原因,因为。

    尽管我现在无法访问SPARC来测试这一点,但根据我在该平台上的经验,我非常确定这条线路是您的问题:

    const unsigned char *location = mem_u_const + 250 - sizeof(int);
    
    mem____const
    块最初是由
    new
    为字符数组分配的。由于
    sizeof(unsigned char)
    是1,而
    sizeof(int)
    是4,因此您要添加246个字节。这不是4的倍数

    在SPARC上,CPU只能读取与4字节边界对齐的4字节字。您试图读取未对齐的单词是导致总线错误的原因


    我建议分配一个
    struct
    ,其中包含一个数组
    unsigned char
    ,后跟一个
    int
    ,而不是一堆指针数学和强制类型转换,就像导致此错误的那样。

    对于没有对齐限制(如SPARC)硬件经验的开发人员来说,这是一个常见问题。x86硬件非常宽容未对齐的访问,尽管会影响性能。其他类型的硬件<代码>SIGBUS

    这行代码:

    int original_var = *((const int*)location);
    
    调用未定义的行为。您正在获取一个
    无符号字符*
    ,并将它指向的内容解释为
    int
    。你不能安全地那样做。时期这是一种未定义的行为——正是因为你所经历的原因

    你违反了严格的别名规则。简而言之,您不能将一种类型的对象引用为另一种类型。
    char*
    没有也不能引用
    int

    Oracle的Solaris Studio编译器实际上提供了一个命令行参数,可以让您在SPARC硬件上不受影响-
    -xmemalign=1i
    (请参阅)。尽管对GCC公平,但如果没有该选项,您在代码中所做的强制操作仍将在Studio编译器下
    SIGBUS


    或者,正如您已经注意到的,您可以使用
    memcpy()
    来复制周围的字节,不管它们是什么-只要您知道源对象可以安全地复制到目标对象中-是的,在某些情况下这是不正确的。

    即使我知道一行中有4个字节?你想说,这样的
    *((const int*)从语言的角度来看,位置
    代码是不正确的,即使开发人员知道
    位置
    指向足够的字节数?除了一行有4个字节外,内存访问必须正确对齐
    int
    。执行访问的机器指令需要这样做,正如链接的answ中指出的那样呃,C++也需要。你不知道我如何从<代码> char */COD>正确的方式获得<代码> int */COD>,如果我知道有一个数组<代码> int */COD>?@ @ AkADy,当你强制<代码> char < /C>地址>引用<代码> int >代码>时,你调用未定义的行为。@为什么一行有4个字节还不够好。除了我的答案之外,正确的做法是在调试器中运行程序,查看哪行代码使程序崩溃,以及导致总线错误的指针包含什么。然后,您可以再次运行并单步执行,以查看它设置为该值的位置。@Davidslor,不幸的是并非总是发生这种情况。它可能在总线错误之前进行多次迭代,并且只在Solaris上显示,因此很难调试。另一种调试技术是添加运行时检查。定义一个内联函数,如:
    template inline bool is_aligned(const T*p){return(uintpttr_T)(void*)(p)%alignof(T)==0;}
    。(如果没有C++11,只需跳过模板并使用常量4,而不是
    alignof(t)
    )然后,每当你做指针数学和转换时,你可以断言指针是对齐的。如果不是,你会立即用错误发生的诊断程序崩溃。哎呀,写下
    type
    ,我的意思是
    class
    typename
    。你想说,mem\u\u const+240会吗读起来没有问题,因为240是4的倍数?我不认为
    new
    保证在分配
    char
    对象时返回4字节对齐的地址,如果可移植性是一个问题,那么当您重新编译64位代码或在另一个体系结构上时,您不希望代码中断。而不是分配字节块,您应该分配一个
    struct
    。如果内存块需要以几种不同的方式解释,请分配
    struct
    s的
    union
    。更可读、优雅、可移植且更安全。但是,如果您确实不想分配一个struct,您可以分配一个
    max\u align\t 。其中一个的存储保证正确对齐,以用作指向任何标量类型的指针。或者您可以使用
    alignas
    。如果我有两个“已接受”,谢谢您的帮助和回答第二个是你的。@BenVoigt你确定吗?他们对
    SIGBUS
    免疫?其中只有一个可以正确地作为SPARC硬件上
    int
    的地址。我在SPARC上使用GCC的经验(承认不久前…)是,他们可能会获得
    SIGBUS
    ,但这可能是b
    int original_var = *((const int*)location);