Как надежно заставить деструктор перезаписывать буфер без его оптимизации в c

#c #gcc #g #clang #destructor

#c #gcc #g #лязг #деструктор

Вопрос:

Я просматриваю чужой код и вижу, что в деструкторе он перезаписывает буфер, который является массивом int внутри класса. Я подумал, что, возможно, компилятор может оптимизировать это.

Итак, я создал небольшую программу для тестирования, и она оптимизирует деструктор.

Единственное простое решение, которое я нашел, — использовать атрибут ((optimize(«O0»))) для деструктора. Но должен быть способ сделать это без использования атрибутов, специфичных для gcc.

Что я пробовал:

  1. Настройка буфера на изменчивость — это работает, если вы напрямую устанавливаете буфер в цикле for (не в memset или тому подобном). Но я действительно не хочу, чтобы это было изменчивым, поскольку это не так.

  2. атрибут ((optimize(«O0»))) работает, но не переносим и выглядит довольно уродливо.

  3. создание собственного нового / удаления с помощью собственного пула памяти. Я подумал, что если компилятор не знает, как используется память, он не должен оптимизировать запись в нее. Тот факт, что это не сработало, кажется ошибкой.

  4. Настройка класса, единственной задачей которого является перезапись буфера в другой памяти. Это работает, но, черт возьми, это бесполезно уродливо.

Я думаю, что многие приложения захотели бы сделать что-то подобное. Не просто обнуление ключей. Наверняка кто-то сталкивался с этой проблемой раньше, или я просто делаю что-то не так.

Идеи?

g memset.cpp -O1 -fsanitize=не определено -Стена -Wextra

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

void print_memory(int *in){
  printf ("n");
  for(int i =0; i < 10; i  ){
    printf(" -:a: x ",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("ndeletedn");
  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: a: 46e87ccd

Комментарии:

1. Вы никогда не вызываете деструктор of example . Вы должны явно вызвать delete it; или создать example статически, используя example it; вместо example* it = new it; предпочтительно последнего (статически создать его), поскольку использование new / delete вообще является запахом кода.

2. Насколько я знаю, люди обычно используют функции ОС, такие как SecureZeroMemory и explicit_bzero для этого, но я предполагаю, что вы ищете что-то в самом языке и стандартной библиотеке.

3. Если класс не владеет буфером, компилятору будет сложнее решить, что код не имеет заметного эффекта, что может помешать оптимизации?

4. Печать массива после удаления указателя — это UB. Разве это не делает этот пример немного бесполезным?

5. Деструктор, похоже, является memsetting … первые 8 байт массива. sizeof(int*) махинации?

Ответ №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);
}
  

или:

 #include <algorithm>
#include <array>
int main() {
    char key[200];
    std::fill<volatile char *>(std::begin(key), std::end(key), 0);
}
  

Комментарии:

1. Хорошая идея! Мне это нравится.

2. Я пробовал оба, и они оба работают (как и ожидалось). Это самое простое решение, которое я видел.