2016-01-26 2 views
0

Я пытаюсь выяснить, кто является компонентами или модулями (может быть, принадлежит ОС?), Которые на самом деле выполняют эти функции, когда приложение или процесс выполняется, и специально запускают команду delete[] X.Что на самом деле происходит и кто несет ответственность за вызов команды [] X?

Вопрос был у меня после того, как я прочитал около delete[] X, и я понимаю, что компилятор отвечает (согласно его реализации) узнать, сколько объектов из X удалить. Но компилятор не является «активным» во время выполнения! Я имею в виду, что во время компиляции компилятор не знает, сколько памяти требуется пользователю в новой команде, а также при удалении, так что же произошло во время выполнения программы?

Один из ответов, которые я прочитал, был чем-то вроде системы времени выполнения, что это? подключен ли он к CPU - потому что CPU выполняет команду в конце концов ... или, может быть, ОС?

Еще один ответ, который я видел, сказал, что это «выполняется распределителем системы» (How does delete[] know how much memory to delete?) - снова где этот компонент (ОС, ЦП)?

ответ

-1

Когда вы используете ключевое слово new, программа запрашивает блок памяти из ОС в куче для удержания объекта. Возвращается указатель на это пространство памяти. Без использования new компилятор помещает объект в стек, и во время компиляции память для этих объектов выравнивается в стеке. Любой объект, созданный с использованием new, должен быть удален, когда его больше не нужно, поэтому важно, чтобы исходный указатель на блок кучи не был потерян, чтобы вы могли называть его удалить. Когда вы используете delete[], он освободит все блоки в массиве. Например, вы используете delete [], если вы создали char* anarray = new char[128], и вы будете использовать delete, если вы сделали string *str = new string(), потому что строка называется объектом, а char * - указателем на массив.

Edit: некоторые объекты перегружать удаления оператора, чтобы ваш объект может поддерживать надлежащее освобождение динамической памяти, поэтому объект может нести ответственность за определение поведения его

2

C++ время выполнения (косвенно) с помощью Operating System примитивов измените virtual address spaceprocess, управляя вашей программой.

Подробнее о computer architecture, CPU modes, operating systems, OS kernels, system calls, instruction sets, machine code, object code, linkers, relocation, name mangling, compilers, virtual memory.

В моей системе Linux, new (при условии стандартной библиотеки C++), как правило, построены выше malloc(3) (при условии стандартной библиотеки C), который может называть mmap(2) системный вызов (реализованный в ядре), который изменяет виртуальное адресное пространство (имея дело с MMU). И delete (из стандартной библиотеки C++), как правило, построено выше free(3), которое может позвонить по номеру munmap(2), который изменяет виртуальное адресное пространство.

вещи гораздо более сложным в деталях:

  • 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 состоит из free software, поэтому я настоятельно рекомендую вам установить некоторое распределение Linux на вашей машине и использовать его. Таким образом, вы можете изучить исходный код из libstdc++ (стандартная библиотека C++, которая является частью исходного кода компилятора GCC, но связана вашей программой), libc (стандартная библиотека C) ядра. Вы также можете использовать strace(1) свою программу и процесс на C++, чтобы понять, что делает system calls.

При использовании GCC, вы можете получить сгенерированный код ассемблера при компиляции исходного файла foo.cc C++ с g++ -Wall -O -fverbose-asm -S foo.cc, который производит файл foo.s ассемблера. Вы также можете получить текстовый вид промежуточного Gimple internal representation внутри компилятора с g++ -Wall -O -fdump-tree-gimple -c foo.cc (вы получите несколько foo.cc.*.gimple и, возможно, многие другие файлы дампа GCC). Вы даже можете найти что-то в представлении Gimple, используя инструмент GCC MELT (я разработал и реализовал большую часть его; useg++ -fplugin=melt -fplugin-arg-melt-mode=findgimple).

Стандартная библиотека C++ имеет внутренние инварианты и условные обозначения, а компилятор C++ отвечает за их использование при испускании кода ассемблера. Поэтому компилятор и его стандартная библиотека C++ разрабатываются совместно и написаны в тесном сотрудничестве (а некоторые грязные трюки внутри реализаций библиотек C++ требуют поддержки компилятора, возможно, благодаря встроенным компиляторам и т. Д.). Это не относится к C++: Ocaml также совместно разрабатывает и совместно реализует язык Ocaml и его стандартную библиотеку.

Система времени выполнения C++ имеет концептуально несколько уровней: стандартную библиотеку C++ libstdc++, стандартную библиотеку C libc, операционную систему (и в нижней части аппаратного обеспечения, включая MMU). Все это детали реализации, языковой стандарт C++11 на самом деле не упоминает их.

+0

Я думаю, что ваш ответ привел меня к тому, что я хочу понять. Если я правильно понял, есть несколько уровней кода, включая код ядра (Linux, Windows и т. Д.), Который использует mmap func, все эти «коды» на самом деле скомпилированы в «коды» сборки, поэтому процессор может его запустить. Что касается удаления - также после компиляции код уже находится в сборке. Теперь, когда процесс (код) загружается в память, процессор фактически запускает его. ** Теперь, на этом этапе процесса, который уже загружен и запущен ** - кто отвечает за обработку памяти? – StackUser

+0

Вы выглядите смущенным, и вам нужно несколько дней прочитать несколько книг по компьютерной архитектуре, наборам инструкций, разработке операционной системы, компиляторам. Мы не можем объяснить все это на SO в нескольких параграфах. –

+0

Хорошо, можете ли вы дать мне несколько отправных точек, хорошие имена книг, ссылки или конкретные слова для поиска начала? – StackUser

2

Компилятор несет ответственность за создание кода, который возникает при возникновении необходимости. Это не нужно запускать, когда это происходит. Сгенерированный код, вероятно, будет вызовом функции в программу, которая делает что-то вдоль этих линий:

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++.

1

Это может быть helpfull

  • для каждого вызова глобального :: оператора нового() будет принимать размер объекта передается и добавить размер дополнительных данных
  • будет выделить блок памяти размера выводимого на предыдущем шаге
  • будет компенсировано указатель на часть блока, не занятой с дополнительными данными и возвратом, что значение смещения к вызывающим

:: оператор delete() сделает то же самое в обратном направлении - сдвиг указателя, доступ к дополнительным данным, освобождение памяти.

И обычно удаление [] используется, когда вы удаляете массив объектов, выделенных в кучу. Поскольку я знаю, что новый [] также добавляет дополнительные данные в начало выделенной памяти, в которой он хранит информацию о размере массива для оператора delete []. Это также может быть useful:

Другими словами, в общем случае блок памяти, выделенный новый [] имеет два набора дополнительных байтов перед фактическим данным: размер блока в байтах (введенный таНос) и счетчик элементов (введен новый []). Второй вариант является необязательным, как демонстрирует ваш пример. Первый, как правило, всегда присутствует, поскольку он безоговорочно выделяется malloc. То есть ваш вызов malloc будет физически выделять более 20 байтов, даже если вы запрашиваете только 20. Эти дополнительные байты будут использоваться malloc для хранения размера блока в байтах. ...

«Дополнительные байты», запрошенные новым [] от оператора new [], не используются для «хранения размера выделенной памяти», как вам кажется. Они используются для хранения количества элементов в массиве, так что delete [] будет знать, сколько деструкторов для вызова. В вашем примере деструкторы тривиальны. Их не нужно называть. Таким образом, нет необходимости выделять эти дополнительные байты и хранить количество элементов.

1

Он работает в два этапа. Один из них - это компилятор, делающий то, что кажется волшебным, а затем куча делает вещи, которые также кажутся волшебными.

То есть, пока вы не поймете трюк. Тогда магия исчезла.

Но кулак позволяет вспомнить, что происходит, когда вы делаете 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) секретная функция, которая устанавливает this указателя вызывает конструктор X. В этом случае по умолчанию один.

Так что при разрушении, мы можем отменить это, если бы мы только могли копить 12 где-то легко найти ...

Я предполагаю, вы уже догадались, где уже ...

везде! действительно! например, он может храниться прямо перед началом объекта.

первая линия становится эти 3 строки:

void* data = malloc((12 * sizeof(X)) +sizeof(int)); 
*((int*)data) = 12; 
data += sizeof(int); 

остальное остается неизменным.

Когда компилятор видит delete [] addr, он знает, что 4 байта до addr он может найти счет объекта. Также необходимо позвонить free(addr - sizeof(int));

Это, по существу, тот же трюк, что и malloc и free. По крайней мере, в старые времена, когда у нас были простые распределители.

Смежные вопросы