gcc - 为什么这个带有 gcc (clang) 内联汇编的简单 c 程序表现出未定义的行为?

标签 gcc x86 inline-assembly gnu-assembler

我正在尝试使用 gcc 汇编器扩展做一件非常简单的事情:

  • 将一个无符号整型变量加载到一个寄存器中
  • 加1
  • 输出结果

在编译我的解决方案时:

#include <stdio.h>
#define inf_int volatile unsigned long long

int main(int argc, char *argv[]){
   inf_int zero = 0;
   inf_int one = 1;
   inf_int infinity = ~0;
   printf("value of zero, one, infinity = %llu, %llu, %llu\n", zero, one, infinity);
   __asm__ volatile (
      "addq $1, %0"
      : "=r" (infinity)
   );
   __asm__ volatile (
      "addq $1, %0"
      : "=r" (zero)
   );
   __asm__ volatile (
      "addq $1, %0"
      : "=r" (one)
   );
   printf("value of zero, one, infinity = %llu, %llu, %llu\n", zero, one, infinity);
   return 0;
}

使用以下开关:

gcc -std=c99 --pedantic -Wall  -c main.c -o main.o
gcc -std=c99 --pedantic -Wall  main.o -o main

我希望运行 main 得到以下结果:

value of zero, one, infinity = 0, 1, 18446744073709551615

value of zero, one, infinity = 1, 2, 0

但我得到的结果是这样的:

value of zero, one, infinity = 0, 1, 18446744073709551615

value of zero, one, infinity = 60, 61, 59

有趣的是,如果我向第一个 printf 添加一个字符,我将得到以下逐一输出:

value of zerao, one, infinity = 0, 1, 18446744073709551615

value of zero, one, infinity = 61, 62, 60

更有趣的是,我可以通过添加(可选)输出寄存器来修复该行为。但这会很浪费,因为使用了 2* 个以上的寄存器,并且无法帮助我理解为什么上一篇文章表现出未定义的行为。

#include <stdio.h>
#define inf_int volatile unsigned long long

int main(int argc, char *argv[]){
   inf_int zero = 0;
   inf_int one = 1;
   inf_int infinity = ~0;
   printf("value of zerao, one, infinity = %llu, %llu, %llu\n", zero, one, infinity);
   __asm__ volatile (
      "addq $1, %0 \n\t"
      "movq %0, %1"
      : "=r" (zero)
      : "r" (zero)
   );
   __asm__ volatile (
      "addq $1, %0 \n\t"
      "movq %0, %1"
      : "=r" (one)
      : "r" (one)
   );
   __asm__ volatile (
      "addq $1, %0 \n\t"
      "movq %0, %1"
      : "=r" (infinity)
      : "r" (infinity)
   );
   printf("value of zero, one, infinity = %llu, %llu, %llu\n", zero, one, infinity);
   return 0;
}

编辑

使用具有相同选项的 clang 编译也会给出未定义的行为:

value of zerao, one, infinity = 0, 1, 18446744073709551615

value of zero, one, infinity = 2147483590, 2147483591, 2147483592

编辑2

按照 Olaf 的建议,我尝试使用 stdint.h 中的 uint64_t。运行程序的结果仍未定义。

#include <stdio.h>
#include <stdint.h>
//#define inf_int volatile unsigned long long
#define inf_int uint64_t
int main(int argc, char *argv[]){
   inf_int zero = 0;
   inf_int one = 1;
   inf_int infinity = ~0;
   printf("value of zerao, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
   __asm__ volatile (
      "addq $1, %0 \n\t"
      : "=r" (zero)
   );
   __asm__ volatile (
      "addq $1, %0 \n\t"
      : "=r" (one)
   );
   __asm__ volatile (
      "addq $1, %0 \n\t"
      : "=r" (infinity)
   );
   printf("value of zero, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
   return 0;
}

最佳答案

您的第一个代码没有为 asm 语句指定任何输入,因此选择的寄存器具有未定义的值(在本例中最初是 printf 的返回值)。第二个示例重复使用未定义值的错误,并通过用输出覆盖输入寄存器来添加更多未定义的行为。

您可以使用两个寄存器,例如:

__asm__ (
   "movq %1, %0 \n\t"
   "addq $1, %0"
   : "=r" (zero)
   : "r" (zero)
);

您可以使用输入/输出参数:

__asm__ (
   "addq $1, %0"
   : "+r" (zero)
);

既可以在内存中也可以在寄存器中:

__asm__ (
   "addq $1, %0"
   : "+rm" (zero)
);

或者您可以将输入与输出联系起来:

__asm__ (
   "addq $1, %0"
   : "=rm" (zero)
   : "0" (zero)
);

最后,不需要任何 volatile 修饰符。

关于gcc - 为什么这个带有 gcc (clang) 内联汇编的简单 c 程序表现出未定义的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31688987/

相关文章:

c++ - 如何找出使用 gcc 时实际应用了哪些优化?

c++ - 即使我尝试递减 t,While 循环也没有终止

c++ - 如何在 VS C++ 中使用 IA32 指令 'fabs'?

c++ - 在内联汇编中调用用户定义的 C 函数

c - 在 C 源代码中省略内联 ASM 代码

c++ - std::declval() 在 GCC 中触发断言错误并发出警告

c++ - gcc (mingw) 中的静态链接

assembly - 将寄存器与其自身进行异或的目的是什么?

assembly - IRQ 编号冲突

c - 如何编写从汇编代码调用的 C 函数