由于 header 中的#define 不匹配导致内存损坏
Memory corruption due to mismatching #define in header
我有3个文件
在 a.h 我有一个 #define ENABLE_STR 包装了一个 std::string str,
我仅在定义 class 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;
}
- 理想情况下,我会假设 compiler/linker 会将此标记为错误,但事实并非如此。
- 为什么地址 sanitizer/valgrind 检测不到这一点,因为这似乎是在覆盖不属于它的内存。
- 除非在 headers 中不使用这样的宏,否则如何检测到它?
I would ideally assume that compiler/linker would flag this as an error but it doesnt.
您为不同翻译单元的相同 class 提供了不同的 class 定义。这是未定义的行为,因此没有编译器 有 来诊断它。正如评论中提到的,编译器开发人员除了编译器用户以这种方式弄乱他们的定义之外还有其他担忧。
在更技术层面上,每个翻译单元发出一个目标文件。目标文件由链接器链接在一起。但是目标文件对 classes 一无所知,只知道函数。因此,它没有关于对象大小或成员偏移量的明确知识。
是否可以在目标文件中发出 "compiler comments" 来检测?或者这可能是调试符号的一部分?是的,但它可能会引入显着的膨胀并增加编译时间。此外,不要求您的库二进制文件具有任何这些,因此在这种情况下它无济于事。因此,在用户搞砸的罕见情况下,这将是一种不可靠的帮助,并具有明显的缺点。
Why could address sanitizer/valgrind not detect this, as this seems like writing over memory that is not owned by it.
我对 valgrind 的内部工作原理了解不够,无法在这里给出一个好的答案,但大概是 get
假设的 str
访问是 i
实际所在的位置inside a
in main
对 valgrind 来说似乎并不立即可疑,因为 str
的开始仍在为 A
分配的内存中。如果您只 get
一个字符,小字符串优化也可能导致 main
永远不会访问 A
为 int
.
保留的前几个字节之外的内容
Except not using macros like this in headers, how would one detect this?
以这种方式使用宏是一个可怕的想法——正是因为这些问题几乎不可能被发现。您自己提到了一些可以快速捕获 "malign" 未定义行为的工具(例如 std::vector 在其控制结构被覆盖后试图管理内存),并且您可以配置操作系统和编译器来检测您的程序当它做了一些可疑的事情时,更严格地得到通知(例如 gcc 上的 -fstack-protector-all
,MSVC 上的 /Gs
和 /RTCs
,these safety features on newer Windows 等等)。
尽管如此,这仍然是我们所说的未定义行为,因此无法保证找到问题的方法,主要是因为"everything works as expected when you try to identify problems"仍在范围内"everything could happen"。或者,换句话说,这些工具可能根本没有发现任何症状,即使您的程序仍然在做细微的错误。
我有3个文件 在 a.h 我有一个 #define ENABLE_STR 包装了一个 std::string str, 我仅在定义 class 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;
}
- 理想情况下,我会假设 compiler/linker 会将此标记为错误,但事实并非如此。
- 为什么地址 sanitizer/valgrind 检测不到这一点,因为这似乎是在覆盖不属于它的内存。
- 除非在 headers 中不使用这样的宏,否则如何检测到它?
I would ideally assume that compiler/linker would flag this as an error but it doesnt.
您为不同翻译单元的相同 class 提供了不同的 class 定义。这是未定义的行为,因此没有编译器 有 来诊断它。正如评论中提到的,编译器开发人员除了编译器用户以这种方式弄乱他们的定义之外还有其他担忧。
在更技术层面上,每个翻译单元发出一个目标文件。目标文件由链接器链接在一起。但是目标文件对 classes 一无所知,只知道函数。因此,它没有关于对象大小或成员偏移量的明确知识。
是否可以在目标文件中发出 "compiler comments" 来检测?或者这可能是调试符号的一部分?是的,但它可能会引入显着的膨胀并增加编译时间。此外,不要求您的库二进制文件具有任何这些,因此在这种情况下它无济于事。因此,在用户搞砸的罕见情况下,这将是一种不可靠的帮助,并具有明显的缺点。
Why could address sanitizer/valgrind not detect this, as this seems like writing over memory that is not owned by it.
我对 valgrind 的内部工作原理了解不够,无法在这里给出一个好的答案,但大概是 get
假设的 str
访问是 i
实际所在的位置inside a
in main
对 valgrind 来说似乎并不立即可疑,因为 str
的开始仍在为 A
分配的内存中。如果您只 get
一个字符,小字符串优化也可能导致 main
永远不会访问 A
为 int
.
Except not using macros like this in headers, how would one detect this?
以这种方式使用宏是一个可怕的想法——正是因为这些问题几乎不可能被发现。您自己提到了一些可以快速捕获 "malign" 未定义行为的工具(例如 std::vector 在其控制结构被覆盖后试图管理内存),并且您可以配置操作系统和编译器来检测您的程序当它做了一些可疑的事情时,更严格地得到通知(例如 gcc 上的 -fstack-protector-all
,MSVC 上的 /Gs
和 /RTCs
,these safety features on newer Windows 等等)。
尽管如此,这仍然是我们所说的未定义行为,因此无法保证找到问题的方法,主要是因为"everything works as expected when you try to identify problems"仍在范围内"everything could happen"。或者,换句话说,这些工具可能根本没有发现任何症状,即使您的程序仍然在做细微的错误。