为什么这个简单的c程序与gcc(clang)内联汇编表现出不确定的行为?

Ada*_*icz 2 x86 gcc gnu-assembler inline-assembly

我正在尝试用gcc汇编程序扩展做一个非常简单的事情:

  • 将unsigned int变量加载到寄存器中
  • 加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;
}
Run Code Online (Sandbox Code Playgroud)

使用以下开关:

gcc -std=c99 --pedantic -Wall  -c main.c -o main.o
gcc -std=c99 --pedantic -Wall  main.o -o main
Run Code Online (Sandbox Code Playgroud)

我希望运行以下结果main:

值为零,一,无穷大= 0,1,18446744073709551615

值为零,一,无穷大= 1,2,0

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

值为零,一,无穷大= 0,1,18446744073709551615

值为零,一,无穷大= 60,61,59

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

zerao的值,1,无穷大= 0,1,18446744073709551615

值为零,一,无穷大= 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;
}
Run Code Online (Sandbox Code Playgroud)

编辑

使用相同选项的clang进行编译也会产生未定义的行为:

zerao的值,1,无穷大= 0,1,18446744073709551615

值为零,一,无穷大= 2147483590,2147483591,2147483592

编辑2

通过奥拉夫的建议,我试着uint64_tstdint.h.运行程序的结果仍未定义.

#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;
}
Run Code Online (Sandbox Code Playgroud)

Tim*_*win 7

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

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

__asm__ (
   "movq %1, %0 \n\t"
   "addq $1, %0"
   : "=r" (zero)
   : "r" (zero)
);
Run Code Online (Sandbox Code Playgroud)

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

__asm__ (
   "addq $1, %0"
   : "+r" (zero)
);
Run Code Online (Sandbox Code Playgroud)

哪个可以在内存和寄存器中:

__asm__ (
   "addq $1, %0"
   : "+rm" (zero)
);
Run Code Online (Sandbox Code Playgroud)

或者您可以将输入绑定到输出:

__asm__ (
   "addq $1, %0"
   : "=rm" (zero)
   : "0" (zero)
);
Run Code Online (Sandbox Code Playgroud)

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