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
!我添加了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相同的结果
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