为什么使用gcc(clang)内联汇编展示未定义行为的简单c程序? [英] Why is this simple c program with gcc (clang) inline assembly exhibiting undefined behaviour?

查看:301
本文介绍了为什么使用gcc(clang)内联汇编展示未定义行为的简单c程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述




  • 将一个unsigned int变量加载到一个寄存器中

    li>
  • 为其添加1

  • 输出结果


编译我的解决方案时:

  #include  
#define inf_int volatile unsigned long long
$ b $ int main(int argc,char * argv []){
inf_int zero = 0;
inf_int one = 1;
inf_int infinity =〜0;
printf(zero,one,infinity =%llu,%llu,%llu \\\
的值,零,一,无穷大);
__asm__ volatile(
addq $ 1,%0
:= r(infinity)
);
__asm__ volatile(
addq $ 1,%0
:= r(zero)
);
__asm__ volatile(
addq $ 1,%0
:= r(one)
);
printf(zero,one,infinity =%llu,%llu,%llu \\\
的值,零,一,无穷大);
返回0;
}

包含以下开关:

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

我期望以下结果来自 main code>:


值为零,one,infinity = 0,1,18446744073709551615

值为零,one,infinity = 1,2,0

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


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

值为零,one,infinity = 60,61,59

有趣的是,如果我向第一个 printf 我得到以下输出:
$ b


zerao的值, one,infinity = 0,1,18446744073709551615

值为零,one,infinity = 61,62,60


更有趣的是,我可以通过添加(可选)输出寄存器来修复这种行为。但是这会浪费,因为使用了2个以上的寄存器,并且不能帮助我理解为什么前面的作品表现出未定义的行为。

  #include< stdio.h> 
#define inf_int volatile unsigned long long
$ b $ int main(int argc,char * argv []){
inf_int zero = 0;
inf_int one = 1;
inf_int infinity =〜0;
printf(zerao的值,one,infinity =%llu,%llu,%llu \ n,零,一,无穷大);
__asm__ volatile(
addq $ 1,%0 \\\
\t
movq%0,%1
:= r(zero)
:r(零)
);
__asm__ volatile(
addq $ 1,%0 \\\
\t
movq%0,%1
:= r(one)
:r(one)
);
__asm__ volatile(
addq $ 1,%0 \\\
\t
movq%0,%1
:= r(infinity)
:r(infinity)
);
printf(zero,one,infinity =%llu,%llu,%llu \\\
的值,零,一,无穷大);
返回0;
}

编辑 $ b

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


值0,1,18446744073709551615

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

blockquote>

edit 2



按照Olaf的建议,我尝试过使用 uint64_t 来自 stdint.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(zerao,one,infinity =%lu,%lu,%lu \\\
,zero,one,infinity);
__asm__ volatile(
addq $ 1,%0 \\\
\t
:= r(zero)
);
__asm__ volatile(
addq $ 1,%0 \\\
\t
:= r(one)
);
__asm__ volatile(
addq $ 1,%0 \\\
\t
:= r(infinity)
);
printf(zero,one,infinity =%lu,%lu,%lu \\\
,zero,one,infinity);
返回0;


解决方案

对asm语句的任何输入,所选择的寄存器都有一个未定义的值(在这种情况下最初是 printf 的返回值)。第二个示例重复使用未定义值的错误,并通过用输出覆盖输入寄存器来增加未定义的行为。



您可以使用两个寄存器:

  __ asm__(
movq%1,%0 \\\
\t
addq $ 1,% 0
:= r(零)
:r(零)
);

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

  __ asm__(
addq $ 1,%0
:+ r(zero)
);

可以在内存中以及寄存器中:

  __ asm__(
addq $ 1,%0
:+ rm(zero)
);

或者你可以将输入连接到输出:

  __ asm__(
addq $ 1,%0
:= rm(零)
:0(零)
);

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


I'm trying to do a very simple thing with gcc assembler extension:

  • load an unsigned int variable into a register
  • add 1 to it
  • output the result

While compiling my solution:

#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;
}

with the following switches:

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

I'd expect the following result from running main:

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

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

but the result I get is this:

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

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

Interestingly, if I add a single char to the first printf I get the following, off-by-one, output:

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

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

Even more interestingly, I can fix the behaviour by adding (optional) output registers. But this would be wasteful because of using 2*more registers, and doesn't help me understand why the previous piece exhibits undefined behaviour.

#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;
}

edit

compiling with clang with the same options gives undefined behaviour as well:

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

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

edit 2

as suggested by Olaf, I've tried with uint64_t from stdint.h. The result of running the program is still undefined.

#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;
}

解决方案

Your first code does not specify any inputs to the asm statements so the chosen register has an undefined value (which in this case was initially the return value of printf). The second example repeats the error of using an undefined value and adds further undefined behaviour by overwriting the input register with the output.

You could use two registers like:

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

You could use an input/output argument:

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

Which can be in memory as well as a register:

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

Or you could tie the input to the output:

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

And finally there is no need for any of the volatile modifiers.

这篇关于为什么使用gcc(clang)内联汇编展示未定义行为的简单c程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆