编译器是否在析构函数中优化memset?

时间:2017-01-06 09:34:29

标签: c++ optimization

给出一个结构:

struct CryptoKey {
    std::vector<unsigned char> key;
    ~CryptoKey() { memset(key.data(),0,key.size()); }
};

编译器标题为以消除对memset的调用,因为这将节省时间,并且没有定义行为的程序可以区分。 (假设析构函数返回后变量key将不复存在。)

然而,像这样的代码在加密应用程序中很有用,因为秘密存储在内存中的时间越少,攻击者提取它的机会就越少。 (memset不提供安全性,但确实提供了“纵深防御”。)

我的问题是,哪些真正的编译器实际消除了这样的memset调用(显然,启用了优化)?

2 个答案:

答案 0 :(得分:7)

也许最好说好的编译器会尝试消除memset调用,开发人员不应该依赖编译器实现的差异来避免这种优化。这些编译器通常具有无法优化的安全替代方案。

memset的安全版

C11引入了memset_s,其中一个特性是不会被优化出来的。

  

与memset不同,任何对memset_s函数的调用都应严格按照(5.1.2.3)中描述的抽象机的规则进行评估。也就是说,对memset_s函数的任何调用都应假定s和n指示的内存在将来可以访问,因此必须包含c指示的值。

Windows特定

在Windows上还有其他选择。 SecureZeroMemory或使用#pragma optimize pragma关闭优化。

常见的子表达式优化

加密安全存在一个更广泛的问题:出于优化原因,编译器有权复制缓冲区。归零可能不会删除所有副本,编译器可能已应用优化,将堆复制到堆栈以消除常见的子表达式。因此,除了避免优化归零之外,还应注意编译器没有插入额外的副本。

答案 1 :(得分:1)

这里的优化器的问题是你的memset根本没有写给成员。是的,key将不复存在,但不是key.data。该内存将返回std::allocator。并且std::allocator很可能会读取相邻内存以确定key.data来自的内存块。典型的实现将这样的数据存储在分配的块的头部中,即在负的偏移处。标题不会更新以反映块是空闲的,或者将空闲块与其他空闲块合并。

这甚至可以内联,因此优化器会看到一个函数执行memset然后执行标头访问。期望优化器能够发现memset是无害的,这是不合理的。尽管如此,分配器可能会保留一个归零块池。