【问题标题】:How to reliably get a destructor to overwrite buffer without it being optimized out in c++如何可靠地让析构函数覆盖缓冲区而不在 C++ 中对其进行优化
【发布时间】:2021-01-19 19:06:54
【问题描述】:

我正在查看其他人的代码,我看到在析构函数中他正在覆盖一个缓冲区,该缓冲区是类中的一个 int 数组。我想也许编译器可能会优化它。

所以我做了一个小程序来测试,它确实优化了析构函数。

我发现的唯一简单解决方案是在析构函数上使用 attribute((optimize("O0"))) 。但是必须有一种方法可以在不使用 gcc 特定属性的情况下做到这一点。

我尝试过的事情:

  1. 将缓冲区设置为易失性 - 如果您直接在 for 循环(而不是 memset 等)中设置缓冲区,则此方法有效。但我真的不希望这是不稳定的,因为它不是。

  2. attribute((optimize("O0"))) 有效,但不可移植,而且有点丑。

  3. 使用自己的内存池创建自己的新/删除。我在想如果编译器不知道内存是如何使用的,它就不应该优化对它的写入。这不起作用的事实似乎是一个错误。

  4. 设置一个唯一的工作就是覆盖另一个内存中的缓冲区。这行得通,但人是不必要的丑陋。

我认为很多应用程序都想做这样的事情。不只是将键归零。肯定有人以前遇到过这个问题,或者我只是做错了什么。

想法?

g++ memset.cpp -O1 -fsanitize=undefined -Wall -Wextra

#include <stdio.h>
#include <string.h>
class example{
public:
  int key[100];
  
  ~example(){
    printf("destructor\n");
    memset(key,0, sizeof(key));
  }
};

void print_memory(int *in){
  printf ("\n");
  for(int i =0; i < 10; i++){
    printf(" %2d:a: %08x ",i, in[i]);
  }
}

int main(){
  example *ptr;
  {
    example *it = new example;
    ptr = it;
    // fill memory with something.
    for(int i = 0; i< 100; i++){
      it->key[i] = rand();
    }
    
    printf("Done randomizing.\n");
    print_memory(it->key);
    delete it;
  }
  printf("\ndeleted\n");
  print_memory(ptr->key);
  printf("\n");
}

输出: 完成随机化。

0:a: 6b8b4567 1:a: 327b23c6 2:a: 643c9869 3:a: 66334873 4:a: 74b0dc51 5:a: 19495cff 6:a: 2ae8944a 7:a: 625558ec 8:a: 238e1f29 9: a: 46e87ccd 析构函数

已删除

0:a:00000000 1:a:00000000 2:a:0e57f010 3:a:0000555b 4:a:74b0dc51 5:a:19495cff 6:a:2ae8944a 7:a:625558ec 8:a:238e1f29 9:答:46e87ccd

【问题讨论】:

  • 你永远不会调用example的析构函数。您必须显式调用delete it; 或静态创建example,使用example it; 而不是example* it = new it; 最好是后者(静态创建它),因为使用new/delete 是一种代码味道。
  • 据我所知,人们通常会使用像 SecureZeroMemoryexplicit_bzero 这样的操作系统功能,但我假设您正在寻找语言和标准库本身的东西。
  • 约翰,你的权利。这是我的示例的损坏版本。我会用删除来更新评论。
  • 如果类不拥有缓冲区,编译器将更难确定代码没有可观察到的效果,因此可能会阻止优化?
  • 析构函数似乎在设置内存...数组的前 8 个字节。 sizeof(int*) 恶作剧?

标签: c++ gcc g++ clang destructor


【解决方案1】:

裸机方式是通过 volatile 访问它 - 这样它就不会被优化。 C方式是:

#include <cstddef>
void volatile_memset(volatile void *s, int c, size_t n) {
    volatile unsigned char *m = reinterpret_cast<volatile unsigned char *>(s);
    while (n--) {
       *m++ = c;
    }
}
int main() {
    char key[200];
    volatile_memset(key, 0, sizeof(key));
}
           

但我认为在 C++ 中你可以:

#include <algorithm>
int main() {
    char key[200];
    std::fill_n<volatile char *>(key, sizeof(key)/sizeof(*key), 0);
}

【讨论】:

  • 好主意!我喜欢它。
  • 我已经尝试了这两种方法,并且它们都可以正常工作(如预期的那样)。这是我见过的最简单的解决方案。
猜你喜欢
  • 2011-04-19
  • 2020-04-06
  • 2021-03-17
  • 1970-01-01
  • 2011-08-20
  • 1970-01-01
  • 2010-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多