mingw64-gcc 上可变参数的可能错误
Possible bug with variadic arguments on mingw64-gcc
我有一个恼人的错误,我试图找出它,然后我创建了一个示例,但我仍然不能 100% 确定它是否是编译器问题。
先给大家介绍一下我用的那个版本吧
x86_64-w64-mingw32-g++ --version
x86_64-w64-mingw32-g++.exe (Rev1, Built by MSYS2 project) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
我知道这不是最新版本,但它是您可以为 MSYS 获得的最新版本。
这是示例代码:
#include <cstdint>
#include <stdio.h>
#include <string.h>
#include <cstdarg>
void test1(){
uint64_t a = 0x3333333333333333;
uint64_t b = 1;
uint64_t c = 2;
uint64_t d = 3;
printf("output should be:\n3 2 1 0 3333333333333333\n");
printf("but output is:\n%llx %llx %llx %llx %llx\n",d,c,b,0,a);
}
void test(uint64_t x1,uint64_t x2,uint64_t x3,uint64_t x4,uint64_t x5,uint64_t x6,
uint64_t x21,uint64_t x22,uint64_t x23,uint64_t x24,uint64_t x25,uint64_t x26,
uint64_t x31,uint64_t x32,uint64_t x33,uint64_t x34,uint64_t x35,uint64_t x36,
uint64_t x41,uint64_t x42,uint64_t x43,uint64_t x44,uint64_t x45,uint64_t x46){
printf("start\n");
}
void test_(){
test(0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776,
0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776,
0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776,
0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776);
}
int main(int argc,char** argv){
test_();
test1();
}
并编译并执行:
x86_64-w64-mingw32-g++ -O0 test.cpp && ./a.exe
现在是令人惊讶的部分,输出是:
start
output should be:
3 2 1 0 3333333333333333
but output is:
3 2 1 7777777700000000 3333333333333333
在上面的示例中,我使用 printf 来生成和可视化问题。
它可能发生在任何其他函数而不是使用变分参数的 printf 上。
例如:void blah(a,b,...)
出于某种原因,编译器做了这件意想不到的事情。
遗憾的是,通过 google 搜索并没有将我引向正确的方向。
这让我想到了一个问题,如果这真的是编译器的问题(linux 没有这样的问题),或者这是一个编程错误(比如忘记转换 0 数字).
看一下反汇编代码,我看到了产生问题的部分:
objdump -M intel -S ./a.exe|egrep -A 30 'test1.+:'
0000000000401570 <_Z5test1v>:
401570: 55 push rbp
401571: 48 89 e5 mov rbp,rsp
401574: 48 83 ec 50 sub rsp,0x50
401578: 48 b8 33 33 33 33 33 movabs rax,0x3333333333333333
40157f: 33 33 33
401582: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
401586: 48 c7 45 f0 01 00 00 mov QWORD PTR [rbp-0x10],0x1
40158d: 00
40158e: 48 c7 45 e8 02 00 00 mov QWORD PTR [rbp-0x18],0x2
401595: 00
401596: 48 c7 45 e0 03 00 00 mov QWORD PTR [rbp-0x20],0x3
40159d: 00
40159e: 48 8d 0d 5b 7a 00 00 lea rcx,[rip+0x7a5b] # 409000 <.rdata>
4015a5: e8 a6 66 00 00 call 407c50 <_Z6printfPKcz>
4015aa: 4c 8b 45 f0 mov r8,QWORD PTR [rbp-0x10]
4015ae: 48 8b 4d e8 mov rcx,QWORD PTR [rbp-0x18]
4015b2: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
4015b6: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8]
4015ba: 48 89 54 24 28 mov QWORD PTR [rsp+0x28],rdx
4015bf: c7 44 24 20 00 00 00 mov DWORD PTR [rsp+0x20],0x0
4015c6: 00
4015c7: 4d 89 c1 mov r9,r8
4015ca: 49 89 c8 mov r8,rcx
4015cd: 48 89 c2 mov rdx,rax
4015d0: 48 8d 0d 59 7a 00 00 lea rcx,[rip+0x7a59] # 409030 <.rdata+0x30>
4015d7: e8 74 66 00 00 call 407c50 <_Z6printfPKcz>
4015dc: 90 nop
4015dd: 48 83 c4 50 add rsp,0x50
4015e1: 5d pop rbp
4015e2: c3 ret
而且我完全不知道为什么它在偏移量 4015bf 上使用那个双字。
也许有人可以阐明我的问题或者能够使用较新的 mingw 版本对其进行测试。
(我已经尝试使用 ubuntu 的 "bionic beaver" docker 图像,但遗憾的是得到了相同的结果......嗯,它具有相同版本的 x86_64-w64-mingw32-g ++ 无论如何)
您 printf
中的 0 类型错误,是 int
而不是 long long
。尝试使用 0ll
作为文字。
当我在 clang
中编译时,我收到此警告:
varby.cpp:12:63: warning: format specifies type 'unsigned long long' but the argument has type 'int' [-Wformat]
这可能是您问题的根源,因为 0
是错误的参数类型。
通过将其设置为 long-long 来修复它:
printf("but output is:\n%llx %llx %llx %llx %llx\n",d,c,b,0LL,a);
一个好的经验法则是,百万分之一的错误是由编译器引起的,因此在可以证明并非如此之前,请始终假定这是您的错。在这种情况下,打开更多警告或尝试在另一个编译器中重现它会发现问题。
您的参数类型不匹配:
printf("but output is:\n%llx %llx %llx %llx %llx\n",d,c,b,0,a);
值 0 的类型为 int
,但 %llx
格式说明符需要一个类型为 unsigned long long int
的变量。使用错误的格式说明符调用 undefined behavior.
因为printf
是可变参数函数,它不能自动将此值转换为正确的类型。所以您需要使用正确的格式说明符:
printf("but output is:\n%llx %llx %llx %d %llx\n",d,c,b,0,a);
或者提出问题中的论点
printf("but output is:\n%llx %llx %llx %llu %llx\n",d,c,b,(unsigned long long)0,a);
或者(在常量的情况下)使用正确的类型后缀
printf("but output is:\n%llx %llx %llx %llu %llx\n",d,c,b,0ULL,a);
我有一个恼人的错误,我试图找出它,然后我创建了一个示例,但我仍然不能 100% 确定它是否是编译器问题。
先给大家介绍一下我用的那个版本吧
x86_64-w64-mingw32-g++ --version
x86_64-w64-mingw32-g++.exe (Rev1, Built by MSYS2 project) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
我知道这不是最新版本,但它是您可以为 MSYS 获得的最新版本。
这是示例代码:
#include <cstdint>
#include <stdio.h>
#include <string.h>
#include <cstdarg>
void test1(){
uint64_t a = 0x3333333333333333;
uint64_t b = 1;
uint64_t c = 2;
uint64_t d = 3;
printf("output should be:\n3 2 1 0 3333333333333333\n");
printf("but output is:\n%llx %llx %llx %llx %llx\n",d,c,b,0,a);
}
void test(uint64_t x1,uint64_t x2,uint64_t x3,uint64_t x4,uint64_t x5,uint64_t x6,
uint64_t x21,uint64_t x22,uint64_t x23,uint64_t x24,uint64_t x25,uint64_t x26,
uint64_t x31,uint64_t x32,uint64_t x33,uint64_t x34,uint64_t x35,uint64_t x36,
uint64_t x41,uint64_t x42,uint64_t x43,uint64_t x44,uint64_t x45,uint64_t x46){
printf("start\n");
}
void test_(){
test(0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776,
0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776,
0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776,
0x7777777777777771,0x7777777777777772,0x7777777777777773,0x7777777777777774,0x7777777777777775,0x7777777777777776);
}
int main(int argc,char** argv){
test_();
test1();
}
并编译并执行:
x86_64-w64-mingw32-g++ -O0 test.cpp && ./a.exe
现在是令人惊讶的部分,输出是:
start
output should be:
3 2 1 0 3333333333333333
but output is:
3 2 1 7777777700000000 3333333333333333
在上面的示例中,我使用 printf 来生成和可视化问题。
它可能发生在任何其他函数而不是使用变分参数的 printf 上。
例如:void blah(a,b,...)
出于某种原因,编译器做了这件意想不到的事情。 遗憾的是,通过 google 搜索并没有将我引向正确的方向。
这让我想到了一个问题,如果这真的是编译器的问题(linux 没有这样的问题),或者这是一个编程错误(比如忘记转换 0 数字).
看一下反汇编代码,我看到了产生问题的部分:
objdump -M intel -S ./a.exe|egrep -A 30 'test1.+:'
0000000000401570 <_Z5test1v>:
401570: 55 push rbp
401571: 48 89 e5 mov rbp,rsp
401574: 48 83 ec 50 sub rsp,0x50
401578: 48 b8 33 33 33 33 33 movabs rax,0x3333333333333333
40157f: 33 33 33
401582: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
401586: 48 c7 45 f0 01 00 00 mov QWORD PTR [rbp-0x10],0x1
40158d: 00
40158e: 48 c7 45 e8 02 00 00 mov QWORD PTR [rbp-0x18],0x2
401595: 00
401596: 48 c7 45 e0 03 00 00 mov QWORD PTR [rbp-0x20],0x3
40159d: 00
40159e: 48 8d 0d 5b 7a 00 00 lea rcx,[rip+0x7a5b] # 409000 <.rdata>
4015a5: e8 a6 66 00 00 call 407c50 <_Z6printfPKcz>
4015aa: 4c 8b 45 f0 mov r8,QWORD PTR [rbp-0x10]
4015ae: 48 8b 4d e8 mov rcx,QWORD PTR [rbp-0x18]
4015b2: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
4015b6: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8]
4015ba: 48 89 54 24 28 mov QWORD PTR [rsp+0x28],rdx
4015bf: c7 44 24 20 00 00 00 mov DWORD PTR [rsp+0x20],0x0
4015c6: 00
4015c7: 4d 89 c1 mov r9,r8
4015ca: 49 89 c8 mov r8,rcx
4015cd: 48 89 c2 mov rdx,rax
4015d0: 48 8d 0d 59 7a 00 00 lea rcx,[rip+0x7a59] # 409030 <.rdata+0x30>
4015d7: e8 74 66 00 00 call 407c50 <_Z6printfPKcz>
4015dc: 90 nop
4015dd: 48 83 c4 50 add rsp,0x50
4015e1: 5d pop rbp
4015e2: c3 ret
而且我完全不知道为什么它在偏移量 4015bf 上使用那个双字。 也许有人可以阐明我的问题或者能够使用较新的 mingw 版本对其进行测试。
(我已经尝试使用 ubuntu 的 "bionic beaver" docker 图像,但遗憾的是得到了相同的结果......嗯,它具有相同版本的 x86_64-w64-mingw32-g ++ 无论如何)
您 printf
中的 0 类型错误,是 int
而不是 long long
。尝试使用 0ll
作为文字。
当我在 clang
中编译时,我收到此警告:
varby.cpp:12:63: warning: format specifies type 'unsigned long long' but the argument has type 'int' [-Wformat]
这可能是您问题的根源,因为 0
是错误的参数类型。
通过将其设置为 long-long 来修复它:
printf("but output is:\n%llx %llx %llx %llx %llx\n",d,c,b,0LL,a);
一个好的经验法则是,百万分之一的错误是由编译器引起的,因此在可以证明并非如此之前,请始终假定这是您的错。在这种情况下,打开更多警告或尝试在另一个编译器中重现它会发现问题。
您的参数类型不匹配:
printf("but output is:\n%llx %llx %llx %llx %llx\n",d,c,b,0,a);
值 0 的类型为 int
,但 %llx
格式说明符需要一个类型为 unsigned long long int
的变量。使用错误的格式说明符调用 undefined behavior.
因为printf
是可变参数函数,它不能自动将此值转换为正确的类型。所以您需要使用正确的格式说明符:
printf("but output is:\n%llx %llx %llx %d %llx\n",d,c,b,0,a);
或者提出问题中的论点
printf("but output is:\n%llx %llx %llx %llu %llx\n",d,c,b,(unsigned long long)0,a);
或者(在常量的情况下)使用正确的类型后缀
printf("but output is:\n%llx %llx %llx %llu %llx\n",d,c,b,0ULL,a);