如何释放C中的调用堆栈?

问题描述 投票:0回答:1

我创建了一个使用 RDP(递归下降解析器)的计算器来解析和评估数学表达式“例如:5cos(30) -5(3+5)”。问题是我还尝试包含像“config”这样的命令,它可以让用户配置角度单位(Gra、Rad、Deg)。而这里我发现了代码设计的问题。

调用堆栈的简化示例

int main()
{
   parseexpr()
    {
       parseterm()
       {
          parsefactor()
          {
              parsecommand() or parsefct() or parsenumber();
          }

       }
    }
}

现在当用户输入“config”命令时。我想让他配置计算器(配置位于 .txt 文件内),然后跳回“main()”(他会被要求再次输入表达式),因为通过调用返回会出现问题堆栈,因为我需要一个返回值,而“parsecommand()”无法返回。

我的问题是如何清理调用堆栈,以便我可以通过“goto”语句直接返回到 main ?

c assembly stack callstack goto
1个回答
2
投票

我的问题是如何清理调用堆栈,以便我可以通过“goto”语句直接返回到 main ?

不要这样做。这其实就是所谓的“XY问题”。

正确的做法是这样的:

  • 将所有这些功能放在自己的模块中,我们称之为

    parse.h / parse.c

  • parse.h
    中提出一个由该模块中所有适用函数使用的结果/错误类型。比如:

    typedef enum
    {
      /* whatever results that may make sense: */
      PARSE_OK,
      PARSE_BAD_FORMAT,
      PARSE_DIV_BY_ZERO,
      ...
    } parse_result_t;
    
  • 适用的功能定义为:

    parse_result_t parse_expr (/* parameters */);
    

    任何需要返回的值都作为指针参数传递。

  • 在嵌套的多个函数调用中,每个函数都会检查结果,如果不正确,则向调用者返回错误代码。通常,在出现错误时不修改值参数被认为是良好的做法。


不正确、非常糟糕的做法是使用

setjmp
/
longjmp
,它们是实际存储和恢复调用堆栈和其他类似变量的低级函数。因此理论上可以用标准 C 实现,但这些函数是不好的做法,因为:

  • 在正确设计的程序中,每个函数都不知道谁调用了它。它只知道它将调用自己的哪些函数。一个正确设计的程序是一个树形依赖结构,main()位于最顶部。
  • 通过结果变量提供“为什么”失败的信息,可以进行各种错误检查/处理,而不仅仅是“哎呀,出了问题”。 通过使用返回值而不是全局状态变量,这些函数是线程安全且独立的。
  • 通过使函数自包含,不存在与不相关函数或变量的“紧密耦合”。例如,如果我选择你的解析库,我不希望出现一些奇怪的链接器错误,指的是缺少一些
  • jmp_buf
  • 全局变量,然后我应该以某种方式在 main() 中实现它。或者更糟糕的是:为了链接
    parse.c
    ,我还必须链接
    main.c
    。这种紧密耦合的依赖关系既不可移植也不可维护,几乎在所有可以想象的方面都是糟糕的。
    自 20 世纪 60 年代以来,无条件跳转到程序中不相关的部分就被认为是糟糕的编程(“GoTo 被认为是有害的”),并被称为“意大利面条编程”——比喻你的程序将开始像一盘意大利面条,其中一根链可能最终到达其他地方的某个意想不到的、不相关的位置。阅读和维护极其困难。
  • setjmp
  • /
    longjmp
    带有一长串危险和潜在的未定义行为。
    
        
© www.soinside.com 2019 - 2024. All rights reserved.