C++ C++;排列

C++ C++;排列,c++,gcc,language-lawyer,endianness,compiler-flags,C++,Gcc,Language Lawyer,Endianness,Compiler Flags,为什么此程序的输出为4 #include <iostream> int main() { short A[] = {1, 2, 3, 4, 5, 6}; std::cout << *(short*)((char*)A + 7) << std::endl; return 0; } 我们从开头向前移动7个字节,然后读取2个字节。我缺少什么?这可以说是GCC中的一个bug。 首先,需要注意的是,由于违反了严格别名规则,您的代码正在调用未定

为什么此程序的输出为
4

#include <iostream>

int main()
{
    short A[] = {1, 2, 3, 4, 5, 6};
    std::cout << *(short*)((char*)A + 7) << std::endl;
    return 0;
}
我们从开头向前移动7个字节,然后读取2个字节。我缺少什么?

这可以说是GCC中的一个bug。

首先,需要注意的是,由于违反了严格别名规则,您的代码正在调用未定义的行为

这就是为什么我认为它是一个错误:

  • 当首次将同一表达式指定给中间
    short
    short*
    时,会导致预期的行为。只有将表达式作为函数参数直接传递时,意外行为才会显现

  • 即使在使用
    -O0-fno严格别名编译时也会发生此错误

  • 我在C中重新编写了代码,以消除任何C++疯狂的可能性。你的问题毕竟被标记为
    c
    !我添加了
    pshort
    函数,以确保不涉及变量性质
    printf

    #include <stdio.h>
    
    static void pshort(short val)
    {
        printf("0x%hx ", val);
    }
    
    int main(void)
    {
        short A[] = {1, 2, 3, 4, 5, 6};
    
    #define EXP ((short*)((char*)A + 7))
    
        short *p = EXP;
        short q = *EXP;
    
        pshort(*p);
        pshort(q);
        pshort(*EXP);
        printf("\n");
    
        return 0;
    }
    
    输出:

    0x500 0x500 0x4
    
    当表达式直接用作参数时,GCC实际上生成了不同的代码,尽管我显然使用了相同的表达式(
    EXP

    使用
    objdump-Mintel-S转储--不显示原始insn endian

    int main(void)
    {
      40054d:   push   rbp
      40054e:   mov    rbp,rsp
      400551:   sub    rsp,0x20
        short A[] = {1, 2, 3, 4, 5, 6};
      400555:   mov    WORD PTR [rbp-0x16],0x1
      40055b:   mov    WORD PTR [rbp-0x14],0x2
      400561:   mov    WORD PTR [rbp-0x12],0x3
      400567:   mov    WORD PTR [rbp-0x10],0x4
      40056d:   mov    WORD PTR [rbp-0xe],0x5
      400573:   mov    WORD PTR [rbp-0xc],0x6
    
    #define EXP ((short*)((char*)A + 7))
    
        short *p = EXP;
      400579:   lea    rax,[rbp-0x16]             ; [rbp-0x16] is A
      40057d:   add    rax,0x7
      400581:   mov    QWORD PTR [rbp-0x8],rax    ; [rbp-0x08] is p
        short q = *EXP;
      400585:   movzx  eax,WORD PTR [rbp-0xf]     ; [rbp-0xf] is A plus 7 bytes
      400589:   mov    WORD PTR [rbp-0xa],ax      ; [rbp-0xa] is q
    
        pshort(*p);
      40058d:   mov    rax,QWORD PTR [rbp-0x8]    ; [rbp-0x08] is p
      400591:   movzx  eax,WORD PTR [rax]         ; *p
      400594:   cwde   
      400595:   mov    edi,eax
      400597:   call   400527 <pshort>
        pshort(q);
      40059c:   movsx  eax,WORD PTR [rbp-0xa]      ; [rbp-0xa] is q
      4005a0:   mov    edi,eax
      4005a2:   call   400527 <pshort>
        pshort(*EXP);
      4005a7:   movzx  eax,WORD PTR [rbp-0x10]    ; [rbp-0x10] is A plus 6 bytes ********
      4005ab:   cwde   
      4005ac:   mov    edi,eax
      4005ae:   call   400527 <pshort>
        printf("\n");
      4005b3:   mov    edi,0xa
      4005b8:   call   400430 <putchar@plt>
    
        return 0;
      4005bd:   mov    eax,0x0
    }
      4005c2:   leave  
      4005c3:   ret
    
    int main(无效)
    {
    40054d:按rbp键
    40054e:mov rbp,rsp
    400551:子rsp,0x20
    短A[]={1,2,3,4,5,6};
    400555:mov字PTR[rbp-0x16],0x1
    40055b:mov字PTR[rbp-0x14],0x2
    400561:mov字PTR[rbp-0x12],0x3
    400567:mov字PTR[rbp-0x10],0x4
    40056d:mov字PTR[rbp-0xe],0x5
    400573:mov字PTR[rbp-0xc],0x6
    #定义EXP((短*)((字符*)A+7))
    short*p=EXP;
    400579:lea-rax,[rbp-0x16];[rbp-0x16]是一个
    40057d:添加rax,0x7
    400581:mov QWORD PTR[rbp-0x8],rax;[rbp-0x08]是p
    短q=*EXP;
    400585:movzx-eax,字PTR[rbp-0xf];[rbp-0xf]是一个加上7个字节的值
    400589:mov字PTR[rbp-0xa],ax;[rbp-0xa]是q
    pshort(*p);
    40058d:mov-rax,QWORD PTR[rbp-0x8];[rbp-0x08]是p
    400591:movzx-eax,单词PTR[rax];*p
    400594:cwde
    400595:mov edi,eax
    400597:拨打400527
    pshort(q);
    40059c:movsx-eax,单词PTR[rbp-0xa];[rbp-0xa]是q
    4005a0:mov edi,eax
    4005a2:拨打400527
    pshort(*EXP);
    4005a7:movzx eax,字PTR[rbp-0x10];[rbp-0x10]是一个加上6个字节的数字********
    4005ab:cwde
    4005ac:mov edi,eax
    4005ae:拨打400527
    printf(“\n”);
    4005b3:mov edi,0xa
    4005b8:拨打400430
    返回0;
    4005bd:mov eax,0x0
    }
    4005c2:离开
    4005c3:ret
    

    • 我从Docker hub获得了与GCC 4.9.4和GCC 5.5.0相同的结果

    您在这里违反了严格的别名规则。你不能把一个对象读到一半就假装它是一个单独的对象。你不能用这样的字节偏移量来创造假想的对象。GCC完全有权做疯狂的sh!I don’我不喜欢回到过去,当你把程序交给艾维斯·普雷斯利的时候,就杀了他

    您可以使用
    char*
    检查和操作组成任意对象的字节。利用这一特权:

    #include <iostream>
    #include <algorithm>
    
    int main()
    {
        short A[] = {1, 2, 3, 4, 5, 6};
    
        short B;
        std::copy(
           (char*)A + 7,
           (char*)A + 7 + sizeof(short),
           (char*)&B
        );
        std::cout << std::showbase << std::hex << B << std::endl;
    }
    
    // Output: 0x500
    
    #包括
    #(包括)
    但您不能只是“虚构”原始集合中不存在的对象


    此外,即使您有一个编译器可以被告知忽略此问题(例如,使用GCC的
    -fno strict aliasing
    开关),组合对象也不能正确地与任何当前主流体系结构对齐。一个
    short
    不能合法地生活在内存†中奇数编号的位置,所以你不能假装那里有一个。根本无法回避原始代码行为的未定义性;事实上,如果您通过GCC的
    -fsanitize=undefined
    开关,它将告诉您同样的信息


    †我正在简化一点。

    由于向
    (short*)
    投射错误对齐的指针,程序具有未定义的行为。这违反了C11中6.3.2.3 p6中的规则,这与其他答案中声称的严格别名无关:

    指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针与引用的类型未正确对齐,则行为未定义

    在[Exp.static .Case] P13C++中,将未对齐的代码> char *<代码>转换为短*<代码>给出了一个未指定的指针值,它可能是无效指针,不能被撤销。
    检查字节的正确方法是通过
    char*
    而不是返回到
    short*
    并假装在
    short
    无法生存的地址存在
    short

    如果这是未定义的行为,我不会感到惊讶。我知道,至少有些CPU架构会捕获未对齐的访问。尝试<代码> MycPyI..()/>将字节转换成<代码>短代码>代码,然后输出。使用什么版本的GCC?@ JONATONHINEHART:<代码> G++5.4.1-2 行为不明确,编译器允许做任何它想做的事。问题标题谈论C,但问题本身是关于C++的。请不要混淆这两种语言,它们是不同的语言。此外,-“在x86 little endian系统上,字符有1个字节,短2个字节”-短2个字节在x86上可能很常见,但这并不能保证。假装非
    “非未定义的行为”@JonathonReinhart:整个前提是从一个
    中读取一个字节,和另一个
    short
    的另一个字节,然后假装它们是一个
    short
    。他们不是。依赖于这样的黑客的代码库要么(a)通过研究或测试,确切地知道他们使用的编译器将做什么
    int main(void)
    {
      40054d:   push   rbp
      40054e:   mov    rbp,rsp
      400551:   sub    rsp,0x20
        short A[] = {1, 2, 3, 4, 5, 6};
      400555:   mov    WORD PTR [rbp-0x16],0x1
      40055b:   mov    WORD PTR [rbp-0x14],0x2
      400561:   mov    WORD PTR [rbp-0x12],0x3
      400567:   mov    WORD PTR [rbp-0x10],0x4
      40056d:   mov    WORD PTR [rbp-0xe],0x5
      400573:   mov    WORD PTR [rbp-0xc],0x6
    
    #define EXP ((short*)((char*)A + 7))
    
        short *p = EXP;
      400579:   lea    rax,[rbp-0x16]             ; [rbp-0x16] is A
      40057d:   add    rax,0x7
      400581:   mov    QWORD PTR [rbp-0x8],rax    ; [rbp-0x08] is p
        short q = *EXP;
      400585:   movzx  eax,WORD PTR [rbp-0xf]     ; [rbp-0xf] is A plus 7 bytes
      400589:   mov    WORD PTR [rbp-0xa],ax      ; [rbp-0xa] is q
    
        pshort(*p);
      40058d:   mov    rax,QWORD PTR [rbp-0x8]    ; [rbp-0x08] is p
      400591:   movzx  eax,WORD PTR [rax]         ; *p
      400594:   cwde   
      400595:   mov    edi,eax
      400597:   call   400527 <pshort>
        pshort(q);
      40059c:   movsx  eax,WORD PTR [rbp-0xa]      ; [rbp-0xa] is q
      4005a0:   mov    edi,eax
      4005a2:   call   400527 <pshort>
        pshort(*EXP);
      4005a7:   movzx  eax,WORD PTR [rbp-0x10]    ; [rbp-0x10] is A plus 6 bytes ********
      4005ab:   cwde   
      4005ac:   mov    edi,eax
      4005ae:   call   400527 <pshort>
        printf("\n");
      4005b3:   mov    edi,0xa
      4005b8:   call   400430 <putchar@plt>
    
        return 0;
      4005bd:   mov    eax,0x0
    }
      4005c2:   leave  
      4005c3:   ret
    
    #include <iostream>
    #include <algorithm>
    
    int main()
    {
        short A[] = {1, 2, 3, 4, 5, 6};
    
        short B;
        std::copy(
           (char*)A + 7,
           (char*)A + 7 + sizeof(short),
           (char*)&B
        );
        std::cout << std::showbase << std::hex << B << std::endl;
    }
    
    // Output: 0x500