0

I am trying to figure out who are the components or modules (maybe belong to the OS?) that actually do the stuff when application or a process is running and specifically run the command delete[] X.

My question came up after I read about delete[] X and I understand that the compiler is responsible (according its implementation) to know how many objects of X to delete. But, the compiler is not "active" at runtime! I mean, at compile time, the compiler does not know how many memory the user need in a new command so, nor it does at delete, so what actually happened at run time when the program actually running?

One of the answers I read about was something called run-time system, what is it? is it connected to the CPU - because the CPU executes the command eventually... or maybe the OS?

Another answer I saw said it "is done by the system's allocator" (How does delete[] know how much memory to delete?) - again where is this component (OS, CPU)?

Community
  • 1
  • 1
StackUser
  • 11
  • 1
  • 3

5 Answers5

2

C++ runtime is (indirectly) using Operating System primitives to change the virtual address space of the process running your program.

Read more about computer architecture, CPU modes, operating systems, OS kernels, system calls, instruction sets, machine code, object code, linkers, relocation, name mangling, compilers, virtual memory.

On my Linux system, new (provided by the C++ standard library) is generally built above malloc(3) (provided by the C standard library) which may call the mmap(2) system call (implemented inside the kernel) which changes the virtual address space (by dealing with the MMU). And delete (from C++ standard library) is generally built above free(3) which may call munmap(2) system call which changes the virtual address space.

Things are much more complex in the details:

  • new is calling the constructor after having allocated memory with malloc

  • delete is calling the destructor before releasing memory with free

  • free usually mark the freed memory zone as reusable by future malloc (so usually don't release memory with munmap)

  • so malloc usually reuses previously freed memory zone before request more address space (using mmap) from the kernel

  • for array new[] and delete[], the memory zone contains the size of the array, and the constructor (new[]) or destructor (delete[]) is called in a loop

  • technically, when you code SomeClass*p = new SomeClass(12); the memory is first allocated using ::operator new (which calls malloc), and then the constructor of SomeClass is called with 12 as argument

  • when you code delete p;, the destructor of SomeClass is called and then the memory is released using ::operator delete (which calls free)

BTW, a Linux system is made of free software, so I strongly suggest you to install some Linux distribution on your machine and use it. So you can study the source code of libstdc++ (the standard C++ library, which is part of the GCC compiler source code but linked by your program), of libc (the standard C library), of the kernel. You could also strace(1) your C++ program and process to understand what system calls it is doing.

If using GCC, you can get the generated assembler code by compiling your foo.cc C++ source file with g++ -Wall -O -fverbose-asm -S foo.cc which produces the foo.s assembler file. You can also get some textual view of the intermediate Gimple internal representation inside the compiler with g++ -Wall -O -fdump-tree-gimple -c foo.cc (you'll get some foo.cc.*.gimple and perhaps many other GCC dump files). You could even search something inside the Gimple representation using the GCC MELT tool (I designed and implemented most of it; use g++ -fplugin=melt -fplugin-arg-melt-mode=findgimple).

The standard C++ library has internal invariants and conventions, and the C++ compiler is responsible to follow them when emitting assembler code. So the compiler and its standard C++ library are co-designed and written in close cooperation (and some dirty tricks inside your C++ library implementations require compiler support, perhaps thru compiler builtins etc...). This is not specific to C++: Ocaml folks also co-design and co-implement the Ocaml language and its standard library.

The C++ runtime system has conceptually several layers: the C++ standard library libstdc++, the C standard library libc, the operating system (and at the bottom the hardware, including the MMU). All these are implementation details, the C++11 language standard don't really mention them.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • I think your answer lead me to what I want to understand. If I get it right, there are several layer of code including the kernel code (Linux, Windows etc..) which uses mmap func, all these "codes" is actually compiled to assembly "codes", so the CPU can run it. As for the delete - also after compile code is already in assembly. Now, when the process (code) is loaded to memory the CPU actually runs it. **Now, in this stage of process that already loaded and running** - who is responsible to handle the memory? – StackUser Jan 27 '16 at 06:08
  • You look confused, and you should take several days to read several books on computer architecture, instruction sets, operating system design, compilers. We cannot explain all that on SO in a few paragraphs. – Basile Starynkevitch Jan 27 '16 at 06:09
  • Ok, can you give me some starting points, good books names, links or kind of specific words to search for a start? – StackUser Jan 27 '16 at 06:12
  • Go into some university library, or look on some web sites describing university courses. I don't understand exactly what you don't know. It is up to you to find additional material. I also suggest to install Linux on your laptop. Learning is a long journey. You'll need [ten years](http://norvig.com/21-days.html). Have a lot of fun & good luck. GCC is free software, so study it – Basile Starynkevitch Jan 27 '16 at 06:13
  • BTYW, did you follow all the links I gave you? You probably would find more references – Basile Starynkevitch Jan 27 '16 at 06:20
  • 1
    I see that you added some links & leading words. Thanks a lot! I will go over them and try lo learn more deeply. Thanks! – StackUser Jan 27 '16 at 06:53
2

The compiler is responsible to generate code that deletes it when the need arises. It doesn't need to be running when it happens. The generated code will probably be a function call to a routine that does something along these lines:

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);
}

When new[] is called, it actually saved the number of elements next to the allocated memory. When you call delete[] it looks up the number count and then deletes that number of elements.

The library that provides these facilities, is called the C++ standard library or the C++ runtime environment. The standard doesn't say anything about what constitutes a runtime, so definitions might differ, but the gist is it's what's need to support running C++ code.

a3f
  • 8,517
  • 1
  • 41
  • 46
1

It might be helpfull

  • for each call to global ::operator new() it will take the object size passed and add the size of extra data
  • it will allocate a memory block of size deduced at previous step
  • it will offset the pointer to the part of the block not occupied with extra data and return that offset value to the caller

::operator delete() will do the same in reverse - shift the pointer, access extra data, deallocate memory.

And usually delete [] uses when you delete an array of object allocated into the heap. As I know new [] also add extra data in the start of allocated memory in which it store information about size of array for delete [] operator. It also could be useful:

In other words, in general case a memory block allocated by new[] has two sets of extra bytes in front of the actual data: the block size in bytes (introduced by malloc) and the element count (introduced by new[]). The second one is optional, as your example demonstrates. The first one is typically always present, as it is unconditionally allocated by malloc. I.e. your malloc call will physically allocate more than 20 bytes even if you request only 20. These extra bytes will be used by malloc to store the block size in bytes. ...

"Extra bytes" requested by new[] from operator new[] are not used to "store the size of allocated memory", as you seem to believe. They are used to store the number of elements in the array, so that the delete[] will know how many destructors to call. In your example destructors are trivial. There's no need to call them. So, there's no need to allocate these extra bytes and store the element count.

Community
  • 1
  • 1
segevara
  • 610
  • 1
  • 7
  • 18
1

It works in two stages. One is the compiler doing things that seem magical and then the heap doing things that also seem magical.

That is, until you realize the trick. Then the magic is gone.

But fist lets recap what happens when you do new X[12];

The code that the compiler writes under the cover conceptually looks like this:

void* data = malloc(12 * sizeof(X))
for (int i=0; i != 12; ++i) {  
  X::Ctor(data);
  data += sizeof(X); 
}

Where Ctor(void* this_ptr) is a secret function that sets the this pointer calls the constructor of X. In this case the default one.

So at destruction, we can undo this, if only we could stash the 12 somewhere easy to find ...

I am guessing you have guessed where already ...

Anyplace! really! for example, it could be store right before the start of the object.

the first line becomes these 3 lines:

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

The rest stays the same.

When the compiler sees delete [] addr it knows that 4 bytes before addr it can find the object count. It also needs to call free(addr - sizeof(int));

This is by the way in essence the same trick that malloc and free use. At least on the old days when we had simple allocators.

AlienRancher
  • 639
  • 4
  • 14
-1

When you use the new keyword, a the program requests a block of memory from the OS on the heap to hold the object. A pointer to that memory space is returned. Without using new the compiler puts the object on the stack and during compile time the memory for those objects is aligned on the stack. Any object created using new needs to be deleted when there is no longer need for it so it is important that the original pointer to the heap block is not lost so you can call delete on it. When you use delete[] it will free all the blocks in an array. Example you use delete[] if you created char* anarray = new char[128] and you would use delete if you did string *str = new string() because a string is referred to as an object and char* is a pointer to an array.

Edit: some objects overload the delete operator so your object can support proper freeing of dynamic memory, so an object could be held responsible for determining the behavior of it

Jake Psimos
  • 640
  • 3
  • 10