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

2023-12-05

我正在尝试使用 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;
}

使用以下开关:

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

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

零、一、无穷大的值 = 0, 1, 18446744073709551615

零、一、无穷大的值 = 1, 2, 0

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

零、一、无穷大的值 = 0, 1, 18446744073709551615

零、一、无穷大的值 = 60、61、59

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

零、一、无穷大的值 = 0, 1, 18446744073709551615

零、一、无穷大的值 = 61, 62, 60

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

#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

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

零、一、无穷大的值 = 0, 1, 18446744073709551615

零、一、无穷大的值 = 2147483590、2147483591、2147483592

edit 2

正如奥拉夫的建议,我尝试过uint64_t from 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("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修饰符。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 如何知道寄存器是否是“通用寄存器”?

    我试图了解寄存器必须具备什么标准才能被称为 通用寄存器 我相信通用寄存器是一个可以用于任何用途的寄存器 用于计算 将数据移入 移出等 并且是一个没有特殊用途的寄存器 现在我读到了ESP寄存器是通用寄存器 我猜是ESP寄存器可以用于任何事情
  • sfinae 与 decltype:clang 或 gcc 中的错误?

    Clang 3 2 可以编译并且代码的行为符合预期 struct have f int f int i return 10 struct empty template
  • 弹出 x86 堆栈以访问函数 arg 时出现分段错误

    我正在尝试链接 x86 程序集和 C 我的C程序 extern int plus 10 int include
  • 如何检查给定调用站点的重载决策集

    如何检查重载解析集 我在多个调用站点中使用了 4 个相互竞争的函数 在一个调用站点中 我期望调用一个函数 但编译器会选择另一个函数 我不知道为什么 这不是微不足道的 为了了解发生了什么 我正在使用enable if disable if打开
  • 字节码和位码有什么区别[重复]

    这个问题在这里已经有答案了 可能的重复 LLVM 和 java 字节码有什么区别 https stackoverflow com questions 454720 what are the differences between llvm
  • 如何在 Linux x86_64 上模拟 iret

    我正在编写一个基于 Intel VT 的调试器 由于当 NMI Exiting 1 时 iret 指令在 vmx guest 中的性能发生了变化 所以我应该自己处理vmx主机中的NMI 否则 guest会出现nmi可重入错误 我查了英特尔手
  • 程序集比较标志理解

    我正在努力理解汇编程序中的以下代码片段 if EAX gt 5 EBX 1 else EBX 2 在汇编程序中 可以写如下 根据我的书 模拟jge操作说明 https www felixcloutier com x86 jcc您通常会使用
  • 什么定义了类型的大小?

    ISO C 标准规定 sizeof char lt sizeof short lt sizeof int lt sizeof long 我在 BIT Linux mint 19 1 上使用 GCC 8 大小为long int is 8 我正
  • CPU寄存器和多任务处理

    我目前正在学习汇编 我很困惑 CPU 寄存器如何与多任务一起工作 所以在多任务系统中 CPU可以随时暂停某个程序的执行并运行另一个程序 那么在这一步中寄存器值是如何保存的呢 寄存器是压入堆栈还是以其他方式 CPU 寄存器如何与多任务一起工作
  • gcc 中 -g 选项的作用是什么

    我看到很多关于 gdb 的教程要求在编译 c 程序时使用 g 选项 我无法理解 g 选项的实际作用 它使编译器将调试信息添加到生成的二进制文件中 此信息允许调试器将代码中的指令与源代码文件和行号相关联 拥有调试符号可以使某些类型的调试 例如
  • 在 C++17 中使用 成员的链接错误

    我在 Ubuntu 16 04 上使用 gcc 7 2 并且需要使用 C 17 中的新文件系统库 尽管确实有一个名为experimental filesystem的库 但我无法使用它的任何成员 例如 当我尝试编译此文件时 include
  • 是否可以在VM内使用VMX CPU指令?

    VM guest 内部的进程是否有可能使用 VMX AMD V VT x CPU 指令 然后由外部 VMM 处理而不是直接在 CPU 上处理 Edit 假设外部VM使用VMX本身来管理其虚拟客户机 即它在Ring 1中运行 如果可能的话 是
  • 在 x86 汇编中将 64 位常量移至内存

    我正在使用 Intel x64 程序集 NASM 编译器 尝试将 0x4000000000000000 常量移至内存 该常量在 ieee 754 标准双精度中应等于 2 0 我正在使用的代码是 define two 0x4000000000
  • 为什么是 ”\?” C/C++ 中的转义序列?

    C C 中有四种特殊的非字母字符需要转义 单引号 双引号 反斜杠 和问号 显然是因为它们有特殊的含义 对于单身char 对于字符串文字 对于转义序列 但为什么是 其中之一 我今天读了教科书上的转义序列表 我意识到我已经never逃脱了 以前
  • 添加冗余赋值可以在未经优化的情况下编译时加快代码速度

    我发现一个有趣的现象 include
  • 如何忽略“有符号和无符号整数表达式之间的比较”?

    谁能告诉我必须使用哪个标志才能使 gcc 忽略 有符号和无符号整数表达式之间的比较 警告消息 gcc Wno sign compare 但你确实应该修复它警告你的比较
  • 为什么 gcc 链接时没有 lpthread 标志?

    我当时正在做一个业余爱好项目 其中互斥体的行为很神秘 我将其归结为这个显然应该陷入僵局的测试用例 include
  • GCC 5 及更高版本中的 AVX2 支持

    我编写了以下类 T 来加速操作 使用 AVX2 的 字符集 然后我发现它不起作用 gcc 5 及更高版本当我使用 O3 时 谁能帮我追踪到一些编程结构 已知不适用于最新的编译器 系统 该代码的工作原理 底层结构 bits 是一个 256 字
  • 为什么pow函数比简单运算慢?

    从我的一个朋友那里 我听说 pow 函数比简单地将底数乘以它的指数的等价函数要慢 例如 据他介绍 include
  • C语言中如何通过内存地址映射函数名和行号?

    如何用 GCC 中的内存地址映射回函数名称和行号 即假设一个 C 语言原型 void func Get the address of caller maybe this could be avoided MemoryAddress get

随机推荐