首页 > 解决方案 > D语言中使用malloc的问题:为什么writeln在这个例子中调用了两次析构函数

问题描述

我正在尝试为使用标准 C malloc 函数存储其数据的 C 库 (libmpdec) 编写 D 包装器。但是我的程序中有一些令人讨厌的错误,我不知道如何解决。所以我写了下面的测试例子,试图理解这一点。这个想法是创建一个结构,该结构包含一个指向在构造函数中使用 malloc 分配的内存区域的指针,其中包含一个以零结尾的 C 字符串,并使用析构函数释放该区域。我也可以使用 printf 打印字符串。当我尝试实现一个 toString() 方法以便可以使用标准 D 函数 writeln 时,问题就出现了。由于某种我不明白的原因,析构函数似乎被调用了两次!(在 writeln 之后),因此会发生分段错误。

import std.stdio;
import core.stdc.stdlib;
import std.string;
import core.stdc.string;

struct Prueba { 

  char* pointer;
  string name;

  this(string given_name)
  {
    writeln("calling the constructor");
    pointer= cast (char*) malloc(char.sizeof*10);
  name=given_name;

    char* p= pointer;
    *p= 'a';
    p++;
    *p= 'b';
    p++;
    *p= '\n';
    p++;
    *p= '\0';
  }

  ~this()
  {
    writeln("\n calling the destructor");
    free(pointer);
  }

  void print()
  {
    printf("Using printf %s \n",pointer); 
  }

  
  string toString()
  {
    ulong len=strlen(pointer);
    return cast(string) pointer[0..len];      
   }

}


void main()
{
  writeln("version 1");
  Prueba p=Prueba("a");
  writeln("using writeln ",p);

  p.print();
}

但是,如果我将结果存储在一个字符串变量中,例如

string s=p.toString();
 writeln("using writeln ",s);

该程序只是工作!我不知道为什么!

您可以在以下位置查看我的测试程序的两个版本

https://github.com/pdenapo/example_programs_in_D/tree/master/using_malloc

非常感谢您的帮助!

更新:似乎 writeln 在这里没有任何作用。我可以用类似的东西得到同样的结果

void probando(Prueba q)
{
    q.print();
}

    
probando(p);

问题似乎是在调用函数时创建了 p 的副本。

标签: mallocd

解决方案


在这种情况下,最好查看是否是同一个实例被销毁。添加&thiswriteln调用中,我得到以下输出:

version 1                            
calling the constructor at 6FBB70F960
Instance on stack: 6FBB70F960        
using writeln ab                     
                                     
 calling the destructor at 6FBB70F820
                                     
 calling the destructor at 6FBB70F7F0

正如我们所看到的,指针是不同的,所以有两个实例。

D 结构是值类型,因此被复制和移动。当您调用带有类参数的函数时,实际上传递的是指针,它基本上表示“您要查找的类实例在那边”。使用结构创建一个副本,突然之间,您有两个独立的对象过着各自独立的生活。

当然,这不是您想要的 - Prueba 实际上不是可复制类型,因为拥有两个副本将导致对析构函数的两次调用,从而导致双重释放。要将其标记为不可复制,只需添加@disable this(this);以禁用postblit,编译器将在创建副本时向您抛出错误消息。

这将导致writeln在线编译器错误,您将不得不手动调用toString,例如:writeln("using writeln ", p.toString());

请注意,不可复制的结构可以传递给函数 asref,因为这不会创建新副本。我们无法真正修改writeln来做到这一点,但对于您自己的功能来说,这是值得了解的。


推荐阅读