删除 const 指针(不是指向 const 的指针)是否合法?

问题描述 投票:0回答:2

Edit1: 现在代码遵循“五规则”。问题依然存在。
Edit2: 现在只将 void* 传递给 printf 的

%p
。问题依然存在。

追踪一些代码中的分段错误,我注意到当一行像

    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();
}
c++ g++ delete-operator
2个回答
0
投票

看到这个已经重新开放了,我将发表我的评论作为答案:

使用 godbolt 的简化版本:https://godbolt.org/z/n1qzrWsdq

delete token->lexer;
完成时,析构函数
~Lexer()
删除其标记,在本例中是 delete 语句中的
token
。此时,您正在删除一个仍在使用的指针,这将是未定义的行为。

从生成的程序集中,在非优化构建中:

        mov     rax, QWORD PTR [rbp-16]
        mov     rax, QWORD PTR [rax]
        mov     rdi, rax
        call    Lexer::~Lexer() [complete object destructor]
        mov     rax, QWORD PTR [rbp-16]
        mov     rax, QWORD PTR [rax]
        mov     esi, 8
        mov     rdi, rax
        call    operator delete(void*, unsigned long)

(其中

[rbp-16]
token
的地址),可以看到after
~Lexer()
被调用,
token
被重新加载。


0
投票

根据 GCC C++ 维护者的说法(正如 apple apple 已经在评论中指出的那样),这是一个自 2012 / v4.6 以来已知的 GCC 错误,即 PR52339。它已经存在于 v4.0 中,但也可以在当前的主控(未来的 v14)或 v11.3 中重现。原因是最后的

delete
中的表达式被求值不止一次,与[expr.delete]:

冲突

4delete-expression 中的 cast-expression 应恰好求值一次。

测试用例:

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
}

命令行

$ g++ main-3.cpp -O2 && ./a.out

但也被

-O0
-m32
触发。

© www.soinside.com 2019 - 2024. All rights reserved.