【问题标题】:Why does this union structure cause a memory leak?为什么这个联合结构会导致内存泄漏?
【发布时间】:2018-12-22 11:10:24
【问题描述】:

我定义了一个联合结构,可以在字符串指针或长指针之间切换(这是由于对齐问题)。结构本身如下所示。

enum {H_STRING, H_LONG};
union Tag
{
    std::string *path;
    long id;
};

struct TextureID
{
    Tag tag;
    int type=H_STRING;

    TextureID()
    {
        type = H_STRING;
        tag.path = new std::string("");
    }
    TextureID(const TextureID& TID)
    {
        type = TID.type;
        if(type==H_STRING)
            tag.path = new std::string(*(TID.tag.path));
        else
            tag.id = TID.tag.id;
    }
    ~TextureID()
    {
        delete(tag.path);

    }

    TextureID& operator= (std::string str)
    {delete(tag.path); tag.path = new std::string(str); type=H_STRING; return *this;}
    TextureID& operator= (long val)
    { if(type==H_STRING) delete(tag.path); tag.id = val; type=H_LONG; return *this;}

    operator std::string&()
    {
        if(type == H_STRING)
        {
            return *(tag.path);
        }
    }
};

std::istream inline &operator>> (std::istream& is, TextureID& TID)
{is >> *(TID.tag.path); TID.type = H_STRING; return is;}

std::ostream inline &operator<< (std::ostream& os, TextureID& TID)
{return os << *(TID.tag.path);}

使用 valgrind,我确定这个数据结构存在内存泄漏。

验证这个结构是内存泄漏原因的方法(即我确定这是原因而不是其他原因的原因)是重载当前正在使用的所有运算符(=,> ) 并且有两个版本的数据结构。第一个是您在上面看到的使用联合的那个。第二个只是在 TextureID 中有一个字符串和一个长 2 个单独的字段。

第二个实现(不使用指针的那个)没有内存泄漏。

我知道如果标签设置为长,这可能会导致分段错误。这不是问题,问题是不知何故,尽管有对 delete() 的显式调用,但分配的内存没有被删除(目前程序中没有任何内容将标记值设置为 long,因此也不会发生 seg 错误)。

编辑:

有人要求我提供内存泄漏证明,所以这里是:

此版本不会导致内存泄漏:

enum {H_STRING, H_LONG};

struct TextureID
{
    std::string path;
    long ID;
    int type=H_STRING;

    TextureID& operator= (std::string str)
    {path = str;}
    TextureID& operator= (long val)
    {ID = val;}

    operator std::string&()
    {
        if(type == H_STRING)
        {
            return (path);
        }
    }
};

std::istream inline &operator>> (std::istream& is, TextureID& TID)
{is >> TID.path; return is;}

std::ostream inline &operator<< (std::ostream& os, TextureID& TID)
{return os << TID.path;}

【问题讨论】:

  • 请注意,在现代 c++ 中,您可以使用 alignas(long) 表示“我希望它像长条一样对齐”(en.cppreference.com/w/cpp/language/alignas)
  • @OlivierSohn 您可以将长字符串(8 字节)对齐(32 字节)吗?我不确定我是否理解。
  • 是的,链接里有说明,可以在成员声明前加上alignas(...),比如:alignas(long) std::string myString;
  • 关于内存泄漏,您能否提供一个显示内存泄漏的程序(带有主程序)的最小示例?
  • @Makogan 您的struct 缺少赋值运算符。因此{TextureID t1; TextureID t2; t1 = t2;} 会出错。

标签: c++ pointers memory memory-leaks unions


【解决方案1】:

您的第一个代码示例中的问题几乎肯定在

TextureID& operator= (std::string str)
{delete(tag.path); tag.path = new std::string(str); type=H_STRING; return *this;}

这里假设tag.path 可以删除。如果不是这样,它将导致未定义的行为 - 例如,如果 type == H_LONG

虽然这是否真的导致泄漏尚有争议,但未定义行为的症状可以是任何东西,包括来自 valgrind 等工具的内存泄漏的虚假报告。

无论如何,一个简单的解决方法是在执行delete tag.path 之前更改此运算符以检查if(type == H_STRING)

第二个示例不会导致泄漏,因为 struct 包含单独的成员,并且默认情况下,编译器将确保析构函数适当地清理所有成员(调用析构函数等)。

正如 PaulMcKenzie 在 cmets 中所指出的,第二个示例还有其他问题,这些问题也可能导致未定义的行为(尽管实际上可能不是内存泄漏)。我将不理会这些问题 - 这超出了所提出的问题。

【讨论】:

  • 其实第二个例子有问题,重载的=函数在声明返回值时不返回值。
  • @Paul - 当然。但是,如果没有调用者使用这些运算符返回的测试用例,那么 OP 就不会出现这种情况。而且,虽然结果(对于调用者)是未定义的,但与第一个代码相比,它不太可能显示为内存泄漏。
  • 是的,如果调用函数,内存泄漏很可能不是未定义行为的结果。如果有的话,是段错误/内存访问冲突。
【解决方案2】:

这看起来不对:

~TextureID()
{
    delete(tag.path);
}

tag.path 仅在设置时才有效(即如果您设置 tag.id 它无效)。因此,您需要确保仅在设置了 tag.path 时才调用 delete。

~TextureID()
{
    if (type == H_STRING) {
        delete(tag.path);
    }
}

【讨论】:

  • 您是否完整阅读了这个问题?我认为currently nothing in the program ever sets the tag value to a long 部分排除了这个问题。但我同意你的观点。
  • 这是为了防止分段错误。你是对的,这是需要的,但这并不能解决我正在谈论的问题。这是内存泄漏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-19
  • 2011-06-25
相关资源
最近更新 更多