多次调用打印函数后未定义的行为

Posted

技术标签:

【中文标题】多次调用打印函数后未定义的行为【英文标题】:Undefined behaviour after multiple calls to a print function 【发布时间】:2019-04-06 08:13:28 【问题描述】:

我正在编写一个基本编译器,但生成的代码无法按预期工作。 我正在使用一种简单的图形着色算法来根据它们的活跃度在寄存器中分配变量。 问题是生成的汇编代码看起来非常好,但在某些时候,它会产生未定义的行为。

如果我不使用寄存器来存储变量,而是使用堆栈,那么一切正常。 我还发现我不能在imull 指令周围使用%edx 寄存器,我想知道%ebx%ecx 现在是否正在发生类似的事情。 我使用gcc -m32 "test.s" runtime.c -o test 编译代码,其中runtime.c 是一个包含打印和输入函数的辅助C 文件。 我还尝试删除程序的某些部分(除最后一个之外的所有打印),然后最后一个打印将起作用。 如果我在最后一次调用之前调用单个打印函数,它将不起作用。

runtime.c 文件:

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

int input() 
    int num;
    char term;
    scanf("%d%c", &num, &term);
    return num;


void print_int_nl(int i) 
    printf("%d\n", i);

源文件:

a = 10
b = input()
c = - 10
d = -input()
print a
print b
print c
print d

生成的汇编代码: https://pastebin.com/ChSRbWgt

编译 .s 文件并使用控制台 (./test) 运行它后,它会要求 2 个输入(如预期的那样)。 我给它 1 和 2。 那么输出是:

10
1
-10
1415880

而不是

10
1
-10
-2

【问题讨论】:

您生成的代码可能没有保留调用约定说应该保留的寄存器。 AFAIR,如果你保留除eaxedx 之外的所有内容,你应该没问题。有关更多详细信息,请参见例如Calling conventions for different C++ compilers and operating systems by Agner Fog. 通过阅读寄存器使用部分,似乎只有%ebx 需要保留(被调用者保存),我确实保留了它。但是,如果我错了,请纠正我,似乎我还应该保留%eax%edx,因为它们可能会收到一些指令的返回值(即%edx 在使用imull 时被替换) @AlexeyFrunze 1. C != C++。 2. 编译器知道要保存什么。它经过数百万开发人员的测试 @P__J__:OP 正在编写自己的编译器,这可能会出错。不过,这不是 minimal reproducible example,因为它们没有显示生成的 asm。 @P__J__ 但我正在编写自己的编译器,所以可能我错过了一些阻止它工作的东西 【参考方案1】:

您需要遵守调用约定(参见例如Calling conventions for different C++ compilers and operating systems by Agner Fog)。

也就是说,你的 C 编译器的约定中有 caller-save 和 callee-save 寄存器。

您生成的代码需要保留被调用者保存寄存器,以便能够返回其 C 调用者。

同样,printf() 将保留被调用者保存寄存器,但它可能会丢弃调用者保存寄存器,这意味着如果您生成的代码调用 printf(),则需要在调用之间保留调用者保存寄存器到 printf() 或任何其他 C 函数。

【讨论】:

【参考方案2】:

您需要在第一次输入后清除输入缓冲区,缓冲区仍然包含换行符,我建议这样做:

int input() 
  int num;
  char term;
  scanf("%d %c", &num, &term);

  return num;

【讨论】:

scanf%d 转换 (like most others, but not %c, %n, or %[) 已经丢弃了前导空格,所以这是没有意义的。请参阅Why is adding a leading space in a scanf format string recommended? 以讨论转换前的某些固定文本等情况,其中 that 之前的空格很有帮助。 %d 之前永远不会有空格,因为它会自行丢弃空格。只有%c(以及字符范围和%n)不这样做。不过,之后%c 之前的空格可能很有用,因为它是%c。可能不需要丢弃字符直到换行符并包括换行符的循环(并且会丢弃整个输入的第一行。)此外,如果 OP 使用 %c 吃尾随换行符,IDK;也许。在这种情况下," %c" 将改为吃掉空格后面的第一个字符。 scanf("%d %c", &amp;num, &amp;term); 具有要求在数字后键入非空白字符的不良副作用 -> scanf("%d%*c", &amp;num); 将消耗待处理的字符,很可能是换行符或空格。更好的是:scanf("%d%*1[\n]", &amp;num); 将使用挂起的换行符(如果有)。

以上是关于多次调用打印函数后未定义的行为的主要内容,如果未能解决你的问题,请参考以下文章

Python--函数1

我的Android进阶之旅如何传递android的log日志打印方法给到底层算法c代码去调用?

我的Android进阶之旅如何传递android的log日志打印方法给到底层算法c代码去调用?

我的Android进阶之旅如何传递android的log日志打印方法给到底层算法c代码去调用?

在linux代码中打印函数调用的堆栈的方法

python基础-函数