· 9 years ago · Oct 24, 2016, 03:00 PM
1# Hello, world!
2
3Please explain in detail what will happen if the following program is executed:
4
5 #include <iostream>
6
7 int main() {
8 std::cout << "Hello, world!" << std::endl;
9 }
10
11## The Novice
12
13It will print out "Hello, world!".
14
15
16## The Apprentice
17
18Assuming a unix system, the program will write the string `"Hello, world!\n"` to
19the standard output stream, which is connected to file descriptor 1. Afterwards,
20the stream is flushed.
21
22
23## The Pedant
24
25The given text is not a program, but rather UTF-8 encoded C++ source code.
26After being turned into a program by a C++ compiler, it's impossible to tell
27what will happen after it is executed. One possibility would be that it receives
28a SIGABRT signal immediately after it started, in which case the effect would
29probably be the creation of a core dump in the current directory.
30
31
32## The Lawyer
33
34Lets analyze the program according to the C++ Draft Standard in version N4296.
35
36Since `<iostream>` is one of the 53 C++ standard library header listed
37in §17.6.1.2/2 [headers], its contents will be made available to the translation
38unit. (§17.6.2.2/1 [using.headers])
39
40Including this header causes the effects of defining an instance
41of `ios_base::Init` with static storage duration. (§27.4.1/2 [iostream.objects.overview])
42During or before construction of this instance, the object `std::cout` of type
43`ostream` is constructed and associated with the object `stdout` declared in
44the `<cstdio>` header. (§27.4.2/1 [narrow.stream.objects])
45
46Next, we see the definition of a global function called `main` returning an int
47and taking no arguments. The program thus fulfills the requirements of
48§3.6.1 [basic.start.main] and this function will be the designated start of
49the program.
50
51The body of the main function consists of an expression statement as in §6.2/1 [stmt.expr].
52The expression inside that statement refers to three distinct entities by name:
53
541) The object `std::cout` of type `std::ostream` (27.4.2 [narrow.stream.objects]),
55
562) The string literal `"Hello, world!"` (a static null-terminated byte string
57 according to §17.5.2.1.4.1/3 Footnote 170), of type `const char[14]`, and
58
593) The function template `std::endl` with the signature `std::basic_ostream<C,T>&(std::basic_ostream<C,T>&)`
60
61These are joined by two instances of the binary left-shift operator, which
62groups left-to-right. Therefore, to determine what will happen, we first have to
63look at the sub-expression `std::cout << "Hello, world!"`, which is the left
64shift operator with operands of type `std::ostream` and `const char[14]`.
65
66Since at least one operand has class or enumeration type, overload resolution is
67used to determine which operator-function or built-in operator is invoked.
68(§13.3.1.2/2 [over.match.oper])
69
70The set of candidate functions is constructed according to the rules detailed
71in §13.3.1.2/3. They consist of the result of the qualified lookup of
72`std::ostream::operator<<` (§13.3.1.2/3.1), together with the result of the
73unqualified lookup of `operator<<` in the context of the expression. (§13.3.1.2/3.2)
74Since the operands can't be converted to a pair of promoted integral types,
75the requirement of clause §13.3.1.2/3.3.3 is not be fulfilled and there are no
76built-in operator candidates.
77
78The best match is the template function specialization
79
80 std::ostream& std::operator<< (std::ostream& out, char const*)
81
82from §27.7.3.6.4 [ostream.inserters.character], which behaves like a formatted
83inserter of out. (§27.7.3.6.1 [ostream.formatted.reqmts])
84
85Therefore, calling this function will begin by constructing an object of
86class `std::sentry`. (§27.7.3.4 [ostream.sentry]) If this object returns `true`
87when converted to bool, the function will proceed to create a character
88sequence `seq` of 14 characters, each widened using out.widen(), to
89insert `seq` into `out`, and to call `width(0)`. (§27.7.3.6.4/3) Finally,
90the sentry object is destroyed before leaving the function, and it
91returns its first argument `out`.
92
93The same procedure is repeated for the next left shift operator, which has
94a left operand of type `std::ostream&` and a right operand that refers to
95a template function of two arguments with the signature
96`template<class C, class T> std::basic_ostream<C,T>&(std::basic_ostream<C,T>&)`.
97Here, the selected overload is
98
99 std::ostream::operator<<(std::ostream&(*f)(std::ostream& os))
100
101from §27.7.3.6.3 [ostream.inserters]. This function returns `f(*this)`,
102and calling `std::endl` has the effect of calling `os.put(os.widen('\n'))`
103followed by `os.flush()`. (§27.7.3.8/1 [ostream.manip])
104
105Finally, control reaches the end of main without encountering a return
106statement, which has has the effect of destroying any objects with automatic
107storage duration and calling `std::exit()` with the argument `0`. (§3.6.1/5)
108
109
110
111## The Idealist
112
113Let's just re-write it in a purely functional way.
114
115## The Ideologue
116
117You should never write a program without unit tests.
118
119## The Engineer
120
121From the lack of any platform specific initialization code, we can
122infer that the program is intended to be run in a hosted as opposed to a
123free-standing environment.
124
125Let's for simplicity assume we're on a standard GNU/Linux system on x86_64.
126Then, our program had a parent process, which gave birth by way of `exec(3)`.
127
128This means it had to store the syscall number 59 in register `$rax`, the virtual
129memory addresses of the file name, the argument array, and the environment array
130in the registers `$rdi`, `$rsi` and `$rdx`, and execute the `SYSCAL`L instruction.
131
132This crosses the border from user space to the kernel, which is now responsible
133for walking the file system to the given path, and opening the file for
134reading. (`<linux>/fs/exec.c:open_exec()`)
135
136If the file exists, has the right permissions etc., the binary format of the
137executable needs to be determined. To do this, the first `BINPRM_BUF_SIZE` bytes
138are loaded into memory (`<linux>/fs/exec.c:prepare_binprm()`), and the list of
139registered binfmt-handlers is walked to see if one of them recognizes the
140format.
141
142Probably we will have an ELF file, which can be recognized by the magic bytes
143`"\x7fELF"` at the start of the file. In this case, the loading will be performed
144by `<linux>/fs/binfmt_elf.c:load_elf_binary()`, where the elf header and the program
145header table are loaded into memory.
146
147First thing to do is look for a `PT_INTERP` section, which contains then name
148of the program interpreter, another ELF executable identified by a fixed path
149on the file system, in our example `"/lib64/ld-linux-x86-64.so.2"`. If there is
150an interpreter, again the kernel needs to locate the correct file, check
151permissions, etc.
152
153After all checks are done and passed, the page table of the old process is
154cleared, and a new mapping set up. All `PT_LOAD` sections of the binary are mapped
155into their respective places, and a memory region for the stack is allocated at
156a random address. Then, the load sections of the interpreter, which is
157position-independent, are mapped into private, write-protected pages at some
158free part of the address space.
159
160When the memory is set up, control is transferred back to user space, in
161particular to the entry point of the interpreter.
162
163The interpreter reads the `DT_NEEDED` tags of the binary to determine the shared
164library dependencies, which will in our case consist of `libstdc++.so.6`, `libc.so.6`,
165`libm.so.6`, and `libgcc_s.so.1`. The interpreter tries to locate each of these
166libraries and map them into memory at a randomly chosen address. A list of
167library load addresses is maintained in the static global `struct _r_debug`. (`/usr/include/link.h`)
168However, unless the environment variable `LD_BIND_NOW` is set to 1,
169the function symbols will not be resolved right now but lazily on the first
170call to the respective function.
171
172After doing its thing, the dynamic loader passes control to the entry
173point of the actual binary, which is the symbol `_start` defined by glibc. (`<glibc>/sysdeps/x86_64/start.S`)
174This starting point will setup an initial stack frame,
175compute the correct values for argc, argv and env from the information in
176the auxiliary vector, and call the C runtime initialization
177function `__libc_start_main`. (`<glibc>/csu/libc-start.c`)
178
179This will run static initialization functions, in particular constructors of all
180static objects, and install atexit-handlers for static destruction
181functions (again, in particular destructors of static objects).
182
183Inside `main()`, the two functions
184
185 _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
186 _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
187
188defined in the shared library `libstdc++.so.6` are called for the first time, so when
189the program jumps to their PLT-slots, a symbol lookup will be triggered. (`<glibc>/elf/dl-lookup.c`)
190
191What these functions do is more or less up to the standard library implementors,
192but ultimately the syscall `write(1, p, 14)` will be issued, where the arguments
193are the file descriptor 1, which is mapped to stdout, a pointer p containing
194the address of the string "Hello, world!\n", and the number of bytes that should
195be written.
196
197Finally, the program returns the process signals to the operating system
198that it is finished and all of its resources should be freed and cleaned up
199by executing the system call `exit_group()`, with the only argument being the
200value returned by `main()` which is 0.
201
202
203## The Physicist
204
205A program must run on a CPU, and a CPU is made of metal.
206Information is transmitted through metal by letting electrons flow along local
207gradients, increasing the entropy of the system.
208All of these electrons, together with the atoms of the CPU, form a huge quantum
209system which will evolve according to its wave function.
210Therefore, we can't know what the program does until we measure it's outcome.
211
212
213## The Enlightened
214
215It will print out "Hello, world!".