On Thu, 4 May 2023, Georg-Johann Lay wrote:
Given the following C++ code:
struct Lexer;
struct Token
{
Lexer* const lexer_;
Token (Lexer *l) : lexer_(l) {}
~Token() = default;
Token() = delete;
Token (const Token&) = delete;
Token (Token&&) = delete;
void operator= (const Token&) = delete;
void operator= (Token&&) = delete;
};
struct Lexer
{
Token *token_;
Lexer() = default;
~Lexer() { delete token_; }
Lexer (const Lexer&) = delete;
Lexer (Lexer&&) = delete;
void operator= (const Lexer&) = delete;
void operator= (Lexer&&) = delete;
};
int main()
{
Lexer *lexer = new Lexer();
Token *token = new Token (lexer);
lexer->token_ = token;
delete token->lexer_;
// delete lexer; // is OK
}
When I compile this with g++ v11.3 (same with g++ from master from
2023-04-20) and run
$ g++ main-3.cpp -Os -W -Wall -Wextra -dumpbase "" -save-temps -dp &&
./a.out
Segmentation fault (core dumped)
The assembly shows that the generated code does two calls to "delete" but
just one call to "new", so it's clear something is going wrong.
As far as I understand, the "delete token_" in ~Lexer is a sequence point, so
that dereferencing token in "delete->lexer_" must be sequenced before calling
~Token ?
Segmentation fault also occurs with -O0, but goes away when removing the
"const" in "Lexer* const lexer_;".
My question: Is this a GCC problem, or a problem with the code and sequence
points?
I think this is a GCC problem. It normally uses a SAVE_EXPR, but seems to
consider token->lexer_ as "safe".
try
{
Lexer::~Lexer ((struct Lexer *) token->lexer_);
}
finally
{
operator delete ((void *) token->lexer_, 8);
}
whereas if I write delete (token->lexer_ + i); where i is 0, I get
try
{
Lexer::~Lexer (SAVE_EXPR <(struct Lexer *) token->lexer_ +
(sizetype) (i * 8)>);
}
finally
{
operator delete ((void *) (SAVE_EXPR <(struct Lexer *)
token->lexer_ + (sizetype) (i * 8)>), 8);
}
which works.
--
Marc Glisse