当调用delete [] X命令时,究竟发生了什么以及谁负责?

时间:2016-01-26 05:41:23

标签: c++ compiler-construction

我试图弄清楚在应用程序或进程运行时实际执行这些操作的组件或模块(可能属于操作系统?)是谁,并专门运行命令delete[] X

我在阅读delete[] X之后提出了我的问题,我理解编译器负责(根据其实现)知道有多少X个对象要删除。但是,编译器在运行时不是“活动的”!我的意思是,在编译时,编译器不知道用户在新命令中需要多少内存,也不知道在删除时是什么内容,那么在程序实际运行时实际发生的是什么呢?

我读到的其中一个答案叫做运行时系统,它是什么?它是否连接到CPU - 因为CPU最终执行命令......或者OS?

我看到的另一个答案是“由系统的分配器完成”(How does delete[] know how much memory to delete?) - 这个组件(操作系统,CPU)又在哪里?

5 个答案:

答案 0 :(得分:2)

C ++运行时(间接)使用Operating System原语来更改运行程序的virtual address spaceprocess

详细了解computer architectureCPU modesoperating systemsOS kernelssystem callsinstruction setsmachine code,{{ 3}},object codelinkersrelocationname manglingcompilers

在我的Linux系统上,new(由C ++标准库提供)通常构建在virtual memory之上(由C标准库提供),可以调用malloc(3)系统调用(在内核中实现,它改变了虚拟地址空间(通过处理mmap(2))。 delete(来自C ++标准库)通常构建在MMU以上,可以调用free(3)系统调用,这会改变虚拟地址空间。

细节上的事情要复杂得多:

  • new在使用malloc

    分配内存后调用构造函数
  • delete在使用free发布内存之前调用析构函数

  • free通常会将已释放的内存区域标记为将来malloc重用(因此通常不要释放内存 munmap

  • 所以malloc通常在从内核请求更多地址空间(使用mmap)之前重用以前释放的内存区域

  • 对于数组new[]delete[],内存区域包含数组的大小,构造函数(new[])或析构函数(delete[])是在循环中调用

  • 从技术上讲,当您编码SomeClass*p = new SomeClass(12);时,首先使用::operator new(调用malloc)分配内存,然后调用SomeClass的构造函数12作为参数

  • 编码delete p;时,会调用SomeClass的析构函数,然后使用::operator delete(调用free)释放内存

BTW,一个Linux系统由munmap(2)组成,所以我强烈建议你在你的机器上安装一些Linux发行版并使用它。因此,你可以研究libstdc++(标准C ++库,它是free software编译器源代码的一部分但是由你的程序链接的)的源代码,{{1内核的(标准C库)。您还可以GCC您的C ++程序和流程来了解它正在做什么strace(1)

如果使用system calls,您可以通过使用生成libc汇编程序文件的foo.cc编译g++ -Wall -O -fverbose-asm -S foo.cc C ++源文件来获取生成的汇编程序代码。您还可以使用foo.s在编译器中获取中间GCC的一些文本视图(您将获得一些g++ -Wall -O -fdump-tree-gimple -c foo.cc以及许多其他GCC转储文件)。您甚至可以使用Gimple internal representation工具在Gimple表示内搜索某些内容(我设计并实现了大部分内容; GCC MELT foo.cc.*.gimple)。

标准C ++库具有内部不变量和约定,C ++编译器负责在发出汇编代码时遵循它们。因此,编译器及其标准C ++库是密切合作设计和编写的(C ++库实现中的一些脏技巧需要编译器支持,可能通过编译器内置等...)。这不是特定于C ++:Ocaml人员还共同设计和共同实现Ocaml语言及其标准库。

C ++运行时系统在概念上有几个层:C ++标准库g++ -fplugin=melt -fplugin-arg-melt-mode=findgimple,C标准库libstdc++,操作系统(以及硬件底部,包括MMU)。所有这些都是实施细节,use语言标准并没有真正提及它们。

答案 1 :(得分:2)

编译器负责在需要时生成delete代码。当它发生时它不需要运行。生成的代码可能是对一行程序的函数调用,它按以下方式执行:

void delete_arr(object *ptr)
{
    size_t *actual_start = ((size_t *)ptr) - 1;
    int count = *actual_start;

    for (int i = count-1; i >= 0; i--)
        destruct(ptr[i]);

    free(actual_start);
}

当调用new[]时,它实际上保存了分配的内存旁边的元素数。当您致电delete[]时,它会查找数字计数,然后删除该数量的元素。

提供这些功能的库称为C ++标准库或C ++运行时环境。该标准没有说明构成运行时的内容,因此定义可能有所不同,但要点是支持运行C ++代码需要什么。

答案 2 :(得分:1)

可能是helpfull

  
      
  • 对于global :: operator new()的每次调用,它将传递对象大小并添加额外数据的大小
  •   
  • 它将分配一个在前一步骤推导出的大小的内存块
  •   
  • 它将指向未被额外数据占用的块部分的指针,并将该偏移值返回给调用者
  •   
     

:: operator delete()将反向移动指针,   访问额外数据,释放内存。

通常删除[]会在删除分配到堆中的对象数组时使用。据我所知new []还在分配的内存的开头添加额外的数据,其中存储有关delete []运算符的数组大小的信息。它也可以是useful

  

换句话说,通常情况下,new []分配的内存块在实际数据前面有两组额外字节:块大小(以字节为单位)(由malloc引入)和元素计数(由new引入) ])。第二个是可选的,正如您的示例所示。第一个通常始终存在,因为它是由malloc无条件分配的。即即使您只请求20,您的malloc调用也将物理分配超过20个字节.Malloc将使用这些额外的字节来存储块大小(以字节为单位)。   ...

     

new []从运算符new []请求的“额外字节”不用于“存储已分配内存的大小”,正如您似乎相信的那样。它们用于存储数组中元素的数量,因此delete []将知道要调用多少个析构函数。在你的例子中,析构函数是微不足道的。没有必要打电话给他们。因此,不需要分配这些额外的字节并存储元素数。

答案 3 :(得分:1)

它分两个阶段运作。一个是编译器做一些看起来很神奇的东西,然后堆做一些看似神奇的东西。

也就是说,直到你意识到这个伎俩。然后神奇的消失了。

但是,让我们回顾一下当你<? if(isset($_POST['add'])){ $title = sf($_POST['title']); $company = sf($_POST['company']); $city = sf($_POST['city']); $filename = ""; if(!empty($_FILES) && !$_FILES['logo']['error']){ // check if there is a "logo" $path = '../img/logo'; // Get extension $extension = strtolower(substr(strrchr($_FILES['logo']['name'], '.'), 1)); $blacklist = array(".php", ".phtml", ".php3", ".php4", ".html", ".htm"); foreach ($blacklist as $item) if(preg_match("/$item\$/i", $_FILES['logo']['name'])) exit; $type = $_FILES['logo']['type']; $size = $_FILES['logo']['size']; $filename = DFileHelper::getRandomFileName($path, $extension); $target = $path . '/' . $filename . '.' . $extension; if (($type != "image/jpg") && ($type != "image/jpeg")) exit; if ($size > 10485760) exit; move_uploaded_file($_FILES['logo']['tmp_name'], $target); } $jb = $mysql->prepare("INSERT INTO tb_job (j_title, j_company, j_city, j_logo) VALUES (?, ?, ?, ?)"); $jb->execute(array($title, $compnay, $city, $filename,)); echo 'OK'; Header("Refresh: 2, /jobs/cv"); } ?> ;

时会发生什么

编译器在封面下编写的代码在概念上如下所示:

new X[12]

其中void* data = malloc(12 * sizeof(X)) for (int i=0; i != 12; ++i) { X::Ctor(data); data += sizeof(X); } 是一个秘密函数,它设置Ctor(void* this_ptr)指针调用X的构造函数。在这种情况下是默认函数。

所以在破坏时,我们可以撤消这个,只要我们可以将12个存放在容易找到的地方......

我猜你已经猜到了......

随地!真!例如,它可以在对象开始之前存储。

第一行成为这3行:

this

其余的保持不变。

当编译器看到void* data = malloc((12 * sizeof(X)) +sizeof(int)); *((int*)data) = 12; data += sizeof(int); 时,它知道delete [] addr之前的4个字节,它可以找到对象计数。它还需要调用addr;

这实际上是free(addr - sizeof(int))malloc使用的相同技巧。至少在过去我们有简单的分配器的时候。

答案 4 :(得分:-1)

当您使用nodetool move关键字时,程序会从堆上的OS请求一块内存来保存该对象。返回指向该内存空间的指针。在不使用new的情况下,编译器将对象放在堆栈上,并且在编译期间,这些对象的内存在堆栈上对齐。使用new创建的任何对象都需要在不再需要时删除,因此重要的是指向堆块的原始指针不会丢失,因此您可以在其上调用delete。当您使用new时,它将释放数组中的所有块。如果您创建了delete[],则使用delete []的示例如果您执行char* anarray = new char[128]则使用delete,因为字符串被称为对象而char *是指向数组的指针。

编辑:一些对象重载了删除操作符,因此您的对象可以支持正确释放动态内存,因此对象可以负责确定它的行为