为什么这个带有 gcc (clang) 内联汇编的简单 c 程序表现出未定义的行为?
Why is this simple c program with gcc (clang) inline assembly exhibiting undefined behaviour?
我正在尝试使用 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 , %0"
: "=r" (infinity)
);
__asm__ volatile (
"addq , %0"
: "=r" (zero)
);
__asm__ volatile (
"addq , %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 , %0 \n\t"
"movq %0, %1"
: "=r" (zero)
: "r" (zero)
);
__asm__ volatile (
"addq , %0 \n\t"
"movq %0, %1"
: "=r" (one)
: "r" (one)
);
__asm__ volatile (
"addq , %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 , %0 \n\t"
: "=r" (zero)
);
__asm__ volatile (
"addq , %0 \n\t"
: "=r" (one)
);
__asm__ volatile (
"addq , %0 \n\t"
: "=r" (infinity)
);
printf("value of zero, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
return 0;
}
您的第一个代码没有指定 asm 语句的任何输入,因此所选寄存器具有未定义的值(在这种情况下,最初是 printf
的 return 值)。第二个示例重复使用未定义值的错误,并通过用输出覆盖输入寄存器来添加更多未定义行为。
您可以使用两个寄存器,例如:
__asm__ (
"movq %1, %0 \n\t"
"addq , %0"
: "=r" (zero)
: "r" (zero)
);
您可以使用 input/output 参数:
__asm__ (
"addq , %0"
: "+r" (zero)
);
既可以在内存中又可以作为寄存器:
__asm__ (
"addq , %0"
: "+rm" (zero)
);
或者您可以将输入与输出联系起来:
__asm__ (
"addq , %0"
: "=rm" (zero)
: "0" (zero)
);
最后不需要任何 volatile
修饰符。
总结一下:
内联汇编不是 C 标准的一部分,它是一个扩展,因此无法保证可移植性(即使在同一硬件上的编译器之间)。
一个好的写法如下:
#include <stdio.h>
#include <stdint.h>
#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 zero, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
__asm__ (
"addq , %0 \n\t"
: "+r" (zero)
);
__asm__ (
"addq , %0 \n\t"
: "+r" (one)
);
__asm__ (
"addq , %0 \n\t"
: "+r" (infinity)
);
printf("value of zero, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
return 0;
}
我正在尝试使用 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 , %0"
: "=r" (infinity)
);
__asm__ volatile (
"addq , %0"
: "=r" (zero)
);
__asm__ volatile (
"addq , %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 , %0 \n\t"
"movq %0, %1"
: "=r" (zero)
: "r" (zero)
);
__asm__ volatile (
"addq , %0 \n\t"
"movq %0, %1"
: "=r" (one)
: "r" (one)
);
__asm__ volatile (
"addq , %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 , %0 \n\t"
: "=r" (zero)
);
__asm__ volatile (
"addq , %0 \n\t"
: "=r" (one)
);
__asm__ volatile (
"addq , %0 \n\t"
: "=r" (infinity)
);
printf("value of zero, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
return 0;
}
您的第一个代码没有指定 asm 语句的任何输入,因此所选寄存器具有未定义的值(在这种情况下,最初是 printf
的 return 值)。第二个示例重复使用未定义值的错误,并通过用输出覆盖输入寄存器来添加更多未定义行为。
您可以使用两个寄存器,例如:
__asm__ (
"movq %1, %0 \n\t"
"addq , %0"
: "=r" (zero)
: "r" (zero)
);
您可以使用 input/output 参数:
__asm__ (
"addq , %0"
: "+r" (zero)
);
既可以在内存中又可以作为寄存器:
__asm__ (
"addq , %0"
: "+rm" (zero)
);
或者您可以将输入与输出联系起来:
__asm__ (
"addq , %0"
: "=rm" (zero)
: "0" (zero)
);
最后不需要任何 volatile
修饰符。
总结一下:
内联汇编不是 C 标准的一部分,它是一个扩展,因此无法保证可移植性(即使在同一硬件上的编译器之间)。
一个好的写法如下:
#include <stdio.h>
#include <stdint.h>
#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 zero, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
__asm__ (
"addq , %0 \n\t"
: "+r" (zero)
);
__asm__ (
"addq , %0 \n\t"
: "+r" (one)
);
__asm__ (
"addq , %0 \n\t"
: "+r" (infinity)
);
printf("value of zero, one, infinity = %lu, %lu, %lu\n", zero, one, infinity);
return 0;
}