【问题标题】:Possible bug with variadic arguments on mingw64-gccmingw64-gcc 上可变参数的可能错误
【发布时间】:2018-05-02 17:52:57
【问题描述】:

我有一个烦人的错误,我试图追踪它,然后我创建了一个示例,但我仍然不能 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 &amp;&amp; ./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,...)

由于某种原因,编译器做了这个意想不到的事情。 遗憾的是,通过谷歌搜索并没有将我引向正确的方向。

如果这真的是编译器的问题(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 上使用该 dword。 也许有人可以阐明我的问题,或者能够使用更新的 mingw-version 对其进行测试。

(我已经尝试过使用 ubuntu 的“仿生海狸”docker 映像,但遗憾的是结果相同......好吧,无论如何它都有相同版本的 x86_64-w64-mingw32-g++)

【问题讨论】:

  • “变分参数”是什么意思?这里没有“可变参数”,这是我最初假设你的意思。

标签: c++ c mingw mingw-w64


【解决方案1】:

您的参数类型不匹配:

 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);

【讨论】:

  • 哇...没想到会这样。他们是否有理由决定默认将数字设为“int”而不是“unsigned long long”?我的意思是可能有很多缺失的错误无法修复,因为您在编译器警告中看不到它们。
  • 好吧,我的问题的根源实际上是 boost python 库:github.com/boostorg/python/blob/develop/src/object/… 我是对的,为了正确使用该库用于 64 位,我需要在其上转换 0地点?
  • @CrazyT 在这种情况下,格式字符串正在寻找指向以空字符结尾的字符串的指针,因此它应该传入(char *)NULL
【解决方案2】:

printf 中的 0 类型错误,它是 int 而不是 long long。尝试使用 0ll 代替文字。

【讨论】:

    【解决方案3】:

    当我在clang 中编译时,我收到了这个警告:

    varby.cpp:12:63: 警告:格式指定类型“unsigned long long”,但参数的类型为“int”[-Wformat]

    这可能是您的问题的根源,因为 0 是错误的参数类型。

    通过将其设为 long-long 来修复它:

    printf("but output is:\n%llx %llx %llx %llx %llx\n",d,c,b,0LL,a);
    

    一个好的经验法则是,一百万分之一的错误将由编译器引起,因此请始终假定这是您的错,直到可以证明并非如此。在这种情况下,打开更多警告或尝试在另一个编译器中重现它会发现问题。

    【讨论】:

    • 嗯,它不能在 linux 编译器上重现,所以我认为它可能是一个编译器错误。
    • 哦,好吧......吸取的教训,'-Wall' 会在我的示例代码上工作。
    • 是的!如有疑问,请打开一切,看看您是否遗漏了一些不太明显的东西。发生在我们最好的人身上。
    猜你喜欢
    • 2014-02-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-09
    • 2014-12-04
    • 1970-01-01
    • 1970-01-01
    • 2016-06-05
    相关资源
    最近更新 更多