【问题标题】:How function declared __declspec(naked) stores local variables?声明的函数 __declspec(naked) 如何存储局部变量?
【发布时间】:2013-11-01 10:38:16
【问题描述】:
__declspec(naked) void printfive() {
   int i = 5;
   printf("%i\n", i);
}

由于某种原因,这段代码有效,但我不明白i 的存储位置?在调用函数的框架中?它成为全局变量?如果它存储在调用者的框架中,那么编译器如何知道位移,因为您可以从具有不同框架大小和局部变量的不同函数中调用printfive()。如果它是全局的,或者类似static 的东西,我尝试过递归,我可以看到变量没有改变,它确实不是真正的本地变量。但这很明显,没有入口代码(序言)。好的,我明白了,没有序言,没有框架,没有寄存器更改,但这是值,但是 范围 会发生什么?此说明符的行为是否在任何参考中定义?这还是 C++ 标准的一部分吗?如果您主要在其中使用asm {}(或使用 asm 调用它们并希望确保该函数没有过度优化),则此类函数非常棒,但您可以与 C++ 混合使用。但这是一种脑筋急转弯。

【问题讨论】:

  • 你用的是什么编译器?
  • "这还是 C++ 标准的一部分吗?"不。你实际上想要达到什么目的?你的目标是什么?你为什么要写一个没有序言/结语的函数?
  • 我正在实现一种类似第四种线程的代码语言,每个原语都是一个汇编程序或 C 函数。主要是 asm,但有时 C 代码更容易阅读,裸函数不是搞砸寄存器。

标签: c++ assembly visual-c++


【解决方案1】:

我知道这个话题已经有好几年了,这是我自己的答案。

由于没有提及有关此主题的 Microsoft 文档,因此对于那些想了解更多有关 Keltar 所述需要或不需要什么的人来说,here 是 Microsoft 文档,它解释了 Keltar 未解释的大部分内容这里。

根据 Microsoft 文档,应该避免使用。

以下规则和限制适用于裸函数:

  • return 语句是不允许的。
  • 不允许结构化异常处理和 C++ 异常处理构造,因为它们必须在堆栈帧中展开。
  • 出于同样的原因,禁止任何形式的 setjmp
  • 禁止使用 _alloca 函数。
  • 为确保在 prolog 序列之前不出现局部变量的初始化代码,初始化的局部变量不 在功能范围内允许。特别是 C++ 的声明
    对象在函数范围内是不允许的。然而,可能会有 嵌套范围内的初始化数据。
  • 不推荐帧指针优化(/Oy 编译器选项),但会自动为裸函数抑制。
  • 您不能在函数词法范围内声明 C++ 类对象。但是,您可以在嵌套块中声明对象。

【讨论】:

    【解决方案2】:

    来自 gcc 手册:

    使用该属性...表示指定的函数确实 不需要由编译器生成的序言/尾声序列。这是 由程序员提供这些序列。唯一的说法 可以安全地包含在裸函数中的是 asm 语句 没有操作数。所有其他声明,包括声明 应避免使用局部变量、if 语句等。裸 函数应该用于实现程序集的主体 函数,同时允许编译器构造必要的 汇编器的函数声明。

    而且它不是标准的(以及 any __declspec__attribute__

    【讨论】:

    • 是的,没有人保证如果你使用“坏”的东西它会起作用。我相信这将取决于实际的 CPU 架构/ABI。坚持使用安全的 asm。
    • @exebook:它恰好在这种情况下有效。但是您是否检查过调用者的堆栈帧仍然完好无损?它的局部变量、返回地址、异常处理的东西?
    【解决方案3】:

    当进入或退出函数时,编译器会添加代码来帮助传递或参数。当一个函数被裸声明时,会生成非参数变量赋值代码,如果要获取任何参数,则需要直接访问相关寄存器或堆栈(取决于 ABI 定义的调用约定)。

    在您的情况下,您没有将参数传递给函数,因此即使函数被声明为赤裸裸的,您的代码也可以工作。如果您想查看差异,请查看反汇编程序。

    【讨论】:

      猜你喜欢
      • 2023-03-24
      • 2014-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-23
      • 1970-01-01
      • 1970-01-01
      • 2011-09-29
      相关资源
      最近更新 更多