C語言學習者第一個程式
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello world\n");
return 0;
}
最近有人在問,去掉{
}
你真的瞭解每一行嗎?我試著回答一下
#include <stdio.h>
int main(int argc, char **argv)
printf("Hello world\n");
return 0;
#include <stdio.h>
使用cpp hello.c > hello.i
可以看到# 1 "hello.c"
...
extern int printf (__const char *__restrict __format, ...);
extern int sprintf (char *__restrict __s,
__const char *__restrict __format, ...) __attribute__ ((__nothrow__));
...
# 2 "hello.c" 2
int main(int argc, char **argv)
{
printf("Hello world\n");
return 1;
}
直接編譯hello.i並執行
$ gcc hello.i
$ ./a.out
Hello world
int main(int argc, char **argv)
從前面文章可以看到gcc編譯時除了link library和本身的object 檔案外,還會多link一些binary,可以從這邊找看看main在那邊。
說明一下,nm顯示的資料中
U
:表示undefined symbol
T
:表示symbol在Text section中
$ nm -A /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o 2> /dev/null |grep main
/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o: U __libc_start_main
/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o: U main
可以看到在crt1.o有一個Undefined symbol叫main,在來看看我們的hello.c這邊
$ nm hello.o
0000000000000000 T main
U puts
所以可以看到crt1.o會用到main(),當然誰去呼叫main這件事我們就視而不見吧。 另外crt1.o
可以從man gcc看到被稱為startup file。
strace
去觀察hello呼叫了哪些system call$ strace ./hello
execve("./hello", ["./hello"], [/* 39 vars */]) = 0
...
write(1, "Hello world\n", 12Hello world
...
exit_group(1)
可以看到事實上printf使用write system call去讓OS印字串到螢幕上。而為什麼不直接用write(1, "Hello world\n", strlen("Hello world\n"));
呢?看TLPI(書)有提到主要原因是system call有代價的,而libc實作了buffer減少system call呼叫的次數。有興趣的可以使用man setvbuf
看看buffer的設定方式。
return 0;
可以看下面的程式碼/* This demos return status */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int rval = 0;
if (argc != 2) {
printf("usage: %s return_status_number(0~255)\n", argv[0]);
printf("Then observe return status in $?\n");
printf("ex: $ %s 121; echo $?\n", argv[0]);
return 2;
}
/* Convert return status to 0~255*/
rval = atoi(argv[1]);
rval = rval % 255;
return rval;
}
跑看看便知道
$ ./return_status ; echo $?
usage: ./return_status return_status_number(0~255)
Then observe return status in $?
ex: $ ./return_status 121; echo $?
2
$ ./return_status 219 ; echo $?
219
$ ./return_status 34 ; echo $?
34
$?
:man bash
找?
可以看到是顯示上次執行回傳狀態。因此不難理解return的值是有人會接起來的。Makefile就使用這個特性判斷build code是否有問題,我們可以測試一下
test:
./return_status 147
$ make test
./return_status 147
make: *** [test] Error 147
gcc -v hello.c
輸出$ gcc -v hello.c
Using built-in specs.
COLLECT_GCC=gcc
...
/usr/lib/gcc/x86_64-linux-gnu/4.6/cc1 -quiet -v -imultilib . -imultiarch x86_64-linux-gnu hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -fstack-protector -o /tmp/cczq4fLe.s
...
as --64 -o /tmp/ccSFUZMm.o /tmp/cczq4fLe.s
...
/usr/lib/gcc/x86_64-linux-gnu/4.6/collect2 --sysroot=/ --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_x86_64 --hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.6 -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../.. /tmp/ccSFUZMm.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o