代码之家  ›  专栏  ›  技术社区  ›  12345ieee

是否有一种安全的方法来指定对象的值可能因为从未使用而未初始化?

  •  5
  • 12345ieee  · 技术社区  · 7 年前

    免责声明:以下是纯学术问题;我将此代码与任何生产系统保持至少100米的距离。这里提出的问题是在任何现实生活中都无法衡量的。

    考虑以下代码( godbolt link ):

    #include <stdlib.h>
    
    typedef int (*func_t)(int *ptr); // functions must conform to this interface
    
    extern int uses_the_ptr(int *ptr);
    extern int doesnt_use_the_ptr(int *ptr);
    
    int foo() {
        // actual selection is complex, there are multiple functions,
        // but I know `func` will point to a function that doesn't use the argument
        func_t func = doesnt_use_the_ptr;
    
        int *unused_ptr_arg = NULL; // I pay a zeroing (e.g. `xor reg reg`) in every compiler
        int *unused_ptr_arg; // UB, gcc zeroes (thanks for saving me from myself, gcc), clang doesn't
        int *unused_ptr_arg __attribute__((__unused__)); // Neither zeroing, nor UB, this is what I want
    
        return (*func)(unused_ptr_arg);
    }
    

    编译器没有合理的方法知道这一点 unused_ptr_arg 是不需要的(因此归零是浪费时间),但我需要,所以我想通知编译器 未使用的\u ptr\u参数 可以具有任何值,例如寄存器中发生的用于将其传递给 func

    有没有办法做到这一点?我知道我远远超出了标准,所以我可以使用特定于编译器的扩展(尤其是gcc&clang)。

    4 回复  |  直到 7 年前
        1
  •  6
  •   Eric Postpischil    7 年前

    使用GCC/Clang'asm'构造

    在GCC和Clang以及其他支持GCCs扩展程序集语法的编译器中,可以执行以下操作:

    int *unused_ptr_arg;
    __asm__("" : "=x" (unused_ptr_arg));
    
    return (*func)(unused_ptr_arg);
    

    那个 __asm__ 建筑 says 这里是一些要插入程序的汇编代码。它将结果写入 unused_ptr_arg 在你选择的任何地方。–of The x 约束意味着编译器可以选择内存、处理器寄存器或机器支持的任何其他内容。)但实际的汇编代码为空( "" )。因此不会生成汇编代码,但编译器认为 未使用的\u ptr\u参数 已初始化。在针对x86-64的Clang 6.0.0和GCC 7.3(编译器资源管理器当前的最新版本)中,这将生成 jmp 没有 xor

    使用标准C

    考虑一下这一点:

    int *unused_ptr_arg;
    (void) &unused_ptr_arg;
    
    return (*func)(unused_ptr_arg);
    

    目的 (void) &unused_ptr_arg; 地址是 未使用的\u ptr\u参数 ,即使未使用地址。这将禁用C 2011[N1570]6.3.2.1 2中的规则,即如果程序使用自动存储持续时间的未初始化对象的值,则行为未定义,该值可以用 register 。由于其地址已被占用,因此无法使用 登记 ,因此根据此规则,使用该值不再是未定义的行为。

    因此,对象具有不确定的值。然后是指针是否具有陷阱表示的问题。如果指针在所使用的C实现中没有陷阱表示,那么仅仅引用该值不会出现陷阱,就像将其作为参数传递一样。

    这个 result with Clang 6.0.0 at Compiler Explorer 是一个 jmp公司 不设置参数寄存器的指令,即使 -Wall -Werror 添加到编译器选项。相反,如果 (void) 行被删除,将导致编译器错误。

        2
  •  2
  •   bolov    7 年前
    int *unused_ptr_arg = NULL;
    

    这是你应该做的。你什么都不用付钱。将an归零 int 这是一个禁忌。从技术上来说,它不是,但实际上是。您永远不会在程序中看到此操作的时间。我不是说它太小了,你不会注意到的。我的意思是,它是如此之小,以至于如此多的其他因素和更长数量级的操作将“吞噬”它。

        3
  •  2
  •   Peter Cordes    7 年前

    由于一个很好的原因,这实际上不可能在所有体系结构中都实现。

    对函数的调用可能需要将其参数溢出到堆栈,而在IA64中,将未初始化的寄存器溢出到堆栈可能会 崩溃 因为之前寄存器的内容是推测性加载,加载了未映射的地址。

        4
  •  1
  •   chux    7 年前

    防止每次运行 int foo() ,只需制作 unused_ptr_arg static

    int foo() {
        func_t func = doesnt_use_the_ptr;
        static int *unused_ptr_arg;    
        return (*func)(unused_ptr_arg);
    }