C Language – Starting with “Hello, World!”
1. Installing the C Compiler
The most widely used compiler for developing C programs in a Linux environment is GCC (GNU Compiler Collection). While it can be installed individually, it is more efficient to install it via the build-essential package, which includes a collection of essential development tools.
$ sudo apt update
$ sudo apt install -y build-essential
$ gcc --version
gcc ...
$ make --version
GNU Make ...
- GCC (GNU Compiler Collection): The standard C compiler in Linux environments.
- build-essential: A package that installs essential development tools at once, including GCC, g++ (C++ compiler), make (build automation tool), and libc6-dev (standard libraries and headers).
2. The First Program
Since the example of printing “Hello, world!” was introduced in the 1978 book The C Programming Language [1], this phrase has been widely used as the most basic introductory example in programming textbooks across almost all programming languages.
2.1. hello.c
First, create a file named "hello.c" and write a program that prints “Hello, world!”.
hello.c
#include <stdio.h>
main() {
printf("Hello, world!\n");
}
Compile the "hello.c" file.
$ gcc hello.c
hello.c:3:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
3 \
main() \
^~~~
As shown above, a warning is generated. This warning occurs because the return type of the main() function was not specified.
※ In the older K&R style, omitting the return type implied int. However, since the C99 standard, it must be explicitly specified [2]. If omitted, the compiler will issue a warning. In C, every function must explicitly declare its return type.
Modify the hello.c code.
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
Recompile the "hello.c" file after modification.
$ gcc hello.c
$ ./a.out
Hello, world!
An executable file named "a.out" is generated, and running it prints "Hello, world!".
2.2. Makefile
Compile by specifying the output file name "hello".
$ gcc -o hello hello.c
$ ./hello
Hello, world!
You can specify the output file name as an argument to gcc. In addition to this, various compilation options can be applied, and compilation can be executed only when files are modified. This is achieved using a Makefile.
Create a script named "Makefile" as follows.
Makefile
TARGET = hello
all: $(TARGET)
$(TARGET): hello.c
gcc -o $(TARGET) hello.c
clean:
rm -f $(TARGET)
Now you can compile using the make command as follows.
$ make
gcc -o hello hello.c
$ make clean
rm -f hello
3. Executable File Analysis
A compiled executable file consists of code and multiple sections. By analyzing it, you can understand how the program operates.
3.1. .rdata (.rodata)
.rdata (ReadOnly Data) or .rodata is a section where read-only data is stored, typically containing constant values that do not change.
$ objdump -s -j .rodata hello
Contents of section .rodata:
2000 01000200 48656c6c 6f2c2077 6f726c64 ....Hello, world
2010 2100
You can confirm that the string "Hello, world!" is stored in the read-only section of the executable file.
3.2. disassemble
By disassembling the executable, you can examine the actual assembly code executed by the CPU.
$ objdump -d -M intel --disassemble=main hello
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push rbp
114e: 48 89 e5 mov rbp,rsp
1151: 48 8d 05 ac 0e 00 00 lea rax,[rip+0xeac] # 0x2004 <_IO_stdin_used+0x4>
1158: 48 89 c7 mov rdi,rax
115b: e8 f0 fe ff ff call 1050 <puts@plt>
1160: b8 00 00 00 00 mov eax,0x0
1165: 5d pop rbp
1166: c3 ret
You can also check the address of a specific symbol using the following command.
$ nm hello | grep _IO_stdin_used
0000000000002000 R _IO_stdin_used
∴ _IO_stdin_used = 0x2000 + 0x04 = 0x2004 → 48656c6c... = Hell...
By calculating the address where the string is stored based on _IO_stdin_used, you can determine that it corresponds to the string "Hello, world!".
※ GCC Optimization: When printf has no format specifier, the compiler automatically replaces it with puts() for more efficient execution.