Повреждение памяти из-за несоответствия #define в заголовке - PullRequest
0 голосов
/ 07 июня 2018

У меня есть 3 файла. У меня есть #define ENABLE_STR, который оборачивает std :: string str, я включаю этот макрос только при определении класса A, но когда я использую A, он пропускается.

Это ситуация, когда a.cpp считает, что есть член str, но main.cpp не знает об этом.И при запуске программы int i перезаписывается на string str.Кажется, ни AddressSanitizer, ни Valgind не определяют это как недопустимый доступ к памяти.

// a.h
#pragma once 
#include <string>
class A
{
   public:
      A();
      std::string& get();
#ifdef ENABLE_STR
      std::string str;
#endif
      int i;
};

// a.cpp
#define ENABLE_STR
#include <iostream>
#include "a.h"

A::A():i(0){ }

std::string& A::get()
{
   std::cin >> str;
   return str;
}

//main.cpp
#include <iostream>
#include "a.h"

int main()
{
   A a;
   std::cout << a.get()  << "\n\n i:" << a.i << std::endl;
}
  • В идеале я бы предположил, что компилятор / компоновщик пометит это как ошибку, но это не так.
  • Почемумог бы обратиться к sanitizer / valgrind, не обнаружив этого, так как это похоже на перезапись памяти, которой он не владеет.
  • За исключением случаев, когда в заголовках не используются подобные макросы, как можно обнаружить это?

1 Ответ

0 голосов
/ 07 июня 2018

В идеале я бы предположил, что компилятор / компоновщик пометит это как ошибку, но это не так.

Вы предоставляете компилятору разные определения классов для одного и того же класса для разных единиц перевода,Это неопределенное поведение, поэтому ни один компилятор не имеет для его диагностики.Как уже упоминалось в комментариях, у разработчиков компилятора есть другие заботы, кроме того, что пользователь компилятора портит свои определения таким образом.

На более техническом уровне каждый модуль перевода генерирует объектный файл.Объектные файлы связаны между собой компоновщиком.Но объектные файлы ничего не знают о классах, только о функциях.Таким образом, он не имеет явных знаний о размерах объектов или смещениях элементов.

Можно ли было бы выдавать "комментарии компилятора" в объектных файлах, чтобы обнаружить это?Или это может быть частью отладочных символов?Да, но это, вероятно, приведет к значительному увеличению и увеличению времени компиляции.Кроме того, не требуется, чтобы ваш двоичный файл библиотеки имел что-либо подобное, так что в этом случае это не поможет.Так что это будет ненадежная помощь в редком случае, когда пользователь испортит, со значительными недостатками.

Почему адрес sanitizer / valgrind не может обнаружить это, так как это похоже на перезапись памяти, которая непринадлежит ему.

Я не знаю достаточно о внутренней работе valgrind, чтобы дать здесь хороший ответ, но предположительно доступ str, который get предполагает, где iфактически находится внутри a в main, кажется, не сразу подозрительным для valgrind, потому что начало str все еще находится в памяти, выделенной для A.Если вы используете get один символ, небольшая строковая оптимизация может также привести к тому, что main никогда не получит доступ к A за пределами тех первых нескольких байтов, зарезервированных для int.

За исключением случаев, когда не используются макросыкак это в заголовках, как можно обнаружить это?

Это ужасная идея использовать макросы таким образом - именно потому, что эти проблемы почти невозможно обнаружить.Вы упомянули о некоторых инструментах, которые могут быстро отлавливать «зловредное» неопределенное поведение (например, std :: vector, пытающийся управлять памятью после перезаписи его структуры управления), и вы можете настроить свою операционную систему и компилятор для более строгого инструментирования вашей программы, чтобыполучать уведомления (например, -fstack-protector-all на gcc, /Gs и /RTCs на MSVC, эти функции безопасности на более новой Windows и т. д.), когда он делает что-то сомнительное.

Тем не менее,это все еще неопределенное поведение , о котором мы говорим, поэтому не существует гарантированного способа найти проблему, в основном потому, что «все работает, как ожидается, когда вы пытаетесь выявить проблемы», все еще находится в сфере «все можетслучиться».Или, другими словами, эти инструменты могут просто не иметь симптомов, обнаруживаемых этими инструментами, даже если ваша программа по-прежнему делает что-то не так.

...