由于标头中的#define不匹配导致内存损坏

时间:2018-06-07 13:26:53

标签: c++ valgrind address-sanitizer

我有3个文件 在a.h中我有一个包含std :: string str的#define ENABLE_STR, 我只在定义A类时才启用此宏,但是当我使用A时它会被遗漏。

这种情况a.cpp认为有str成员,但main.cpp不知道。当程序运行int istring 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 :(得分:2)

  

我理想地假设编译器/链接器会将此标记为错误,但它不会。

您为不同的翻译单元为同一个类提供了不同的类定义。这是未定义的行为,因此没有编译器具有来诊断它。正如评论中所提到的,编译器开发人员还有其他担忧,而不是编译器用户以这种方式弄乱他们的定义。

在更技术层面上,每个翻译单元都会发出一个目标文件。目标文件由链接器链接在一起。但是目标文件不知道类,只知道函数。因此,它没有关于对象大小或成员偏移的明确知识。

是否可以在目标文件中发出“编译器注释”来检测?或者这可能是调试符号的一部分?是的,但它可能会引入显着的膨胀并增加编译时间。此外,不要求您的库二进制文件具有任何这些,因此在这种情况下它没有帮助。因此,在极少数用户搞砸的情况下,这将是一种不可靠的帮助,具有明显的缺点。

  

为什么解决sanitizer / valgrind无法检测到这一点,因为这似乎是在写入不属于它的内存。

我对valgrind的内部工作原理不太了解,不能在这里给出一个好的答案,但可能str get假设i实际上在a内的main { str中的{1}}对valgrind似乎没有立即可疑,因为A的开头仍然在为get分配的内存中。如果您只main个字符,那么小字符串优化也可能导致A永远不会在为int保留的前几个字节之外访问-fstack-protector-all

  

除了在标题中不使用这样的宏之外,如何检测到它?

以这种方式使用宏是一个可怕的想法 - 正是因为这些问题几乎无法被发现。你自己提到了一些工具可能会很快发现“恶意”未定义的行为(例如std :: vector试图在覆盖控制结构后管理内存),你可以配置你的操作系统和编译器来更严格地检测你的程序当它做了可疑的事情时,会收到通知(例如,关于gcc的/Gs,关于MSVC的/RTCs和{{1}},these safety features on newer Windows等等。

尽管如此,这仍然是我们正在谈论的未定义的行为,所以没有保证找到问题的方法,主要是因为“当你试图发现问题时,一切按预期工作”仍在“一切都可能发生”的领域。或者,换句话说,即使您的程序仍然存在微妙的错误,也可能根本没有这些工具捕获的症状。