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.