编辑1:现在代码遵循“五个规则”。问题仍然存在。
编辑2:现在只传递void*给printf的%p
。问题仍然存在。
编辑3:tl;dr:这是一个GCC bug。
追踪一段代码中的分段错误,我注意到当出现像这样的一行时
Lexer* const lexer_;
如果存在一个属性,代码会崩溃;而没有使用const
时,它可以正常工作。
上述位置允许使用const
吗?
供参考,下面是一个来自更大程序的C-Reduce'd C++代码,展示了这个问题。不幸的是,C-Reduce在某个点开始将标识符混淆为单个字母,所以我停止了简化,并尽可能使代码整洁。编译时,我使用的是Linux x86_64上的g++ v11.3。
> g++ main.cpp -o main.x -fsanitize=address -Werror=all -Werror=extra
运行时,它会打印出来。
0x602000000010 = new Lexer
0x602000000030 = new Token
0x7ffca90b51f0 = new Expression
0x7ffca90b51f0 = start delete Expression
0x602000000010 = start delete Lexer
0x602000000030 = delete Token
0x602000000010 = done delete Lexer
=================================================================
==1232849==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000030 at pc 0x556fc889953d bp 0x7ffca90b5190 sp 0x7ffca90b5180
READ of size 8 at 0x602000000030 thread T0
#0 0x556fc889953c in ExpressionParser::Expression::~Expression() (.../main.x+0x153c)
...
0x602000000030 is located 0 bytes inside of 8-byte region [0x602000000030,0x602000000038)
freed by thread T0 here:
#0 0x7f5258f6f22f in operator delete(void*, unsigned long) .../libsanitizer/asan/asan_new_delete.cpp:172
#1 0x556fc889965f in ExpressionParser::Lexer::~Lexer() (.../main.x+0x165f)
...
previously allocated by thread T0 here:
#0 0x7f5258f6e1c7 in operator new(unsigned long) .../libsanitizer/asan/asan_new_delete.cpp:99
#1 0x556fc8899588 in ExpressionParser::Lexer::tokenize() (.../main.x+0x1588)
...
SUMMARY: AddressSanitizer: heap-use-after-free (/home/john/own/C/mp-gmp/const-problem/main-2.x+0x153c) in ExpressionParser::Expression::~Expression()
...
使用
-D CONST=
使lexer_
成为非常量,代码运行正常并打印出以下内容:0x602000000010 = new Lexer
0x602000000030 = new Token
0x7ffff44937e0 = new Expression
0x7ffff44937e0 = start delete Expression
0x602000000010 = start delete Lexer
0x602000000030 = delete Token
0x602000000010 = done delete Lexer
0x7ffff44937e0 = end delete Expression
另外一个可行的方法是使用virtual ~Lexer();
;但是这应该是不需要的,因为Lexer
没有虚方法。
来源
#include <cstdio>
#ifndef CONST
#define CONST const
#endif
class ExpressionParser
{
public:
class Token;
class Lexer;
class Expression
{
friend ExpressionParser;
Expression (Token *token) : expression_(token)
{
printf ("%p = new Expression\n", (void*) this);
}
Expression (const Expression&) = delete;
Expression (Expression&&) = delete;
void operator= (const Expression&) = delete;
void operator= (Expression&&) = delete;
~Expression();
Token *expression_;
};
static void eval();
};
using EP = ExpressionParser;
class EP::Lexer
{
public:
Token *tokens_ = nullptr;
Lexer()
{
printf ("%p = new Lexer\n", (void*) this);
}
Lexer (const Lexer&) = delete;
Lexer (Lexer&&) = delete;
void operator= (const Lexer&) = delete;
void operator= (Lexer&&) = delete;
~Lexer();
void tokenize();
};
class EP::Token
{
friend ExpressionParser;
Lexer * CONST lexer_;
Token (Lexer *lexer) : lexer_(lexer)
{
printf ("%p = new Token\n", (void*) this);
}
Token (const Token&) = delete;
Token (Token&&) = delete;
void operator= (const Token&) = delete;
void operator= (Token&&) = delete;
~Token()
{
printf ("%p = delete Token\n", (void*) this);
}
};
void EP::eval()
{
Lexer *lexer = new Lexer();
lexer->tokenize();
(void) Expression (lexer->tokens_);
}
EP::Expression::~Expression()
{
printf ("%p = start delete Expression\n", (void*) this);
delete expression_->lexer_;
printf ("%p = end delete Expression\n", (void*) this);
}
void EP::Lexer::tokenize()
{
tokens_= new Token (this);
}
EP::Lexer::~Lexer()
{
printf ("%p = start delete Lexer\n", (void*) this);
delete tokens_;
printf ("%p = done delete Lexer\n", (void*) this);
}
int main (void)
{
ExpressionParser::eval();
}