本系列博客是根據視頻:http://watchmen.cn/forum.php?mod=forumdisplay&fid=50做的筆記
目錄
Linux 下編輯調試工具:gcc,gdb。
代碼編譯的四個步驟
gcc是把高級語言編譯成二進制可執行代碼的工具,需要經歷四個步驟,
代碼如下:
#include <stdio.h>
#define pi 3.14
int main (void)
{
printf("Hello Word,%f\n",pi);
return 0;
}
(1) 預處理:去掉註釋,進行宏替換(#define 相關),頭文件(#include)包含等工作。
[wlsh@wlsh-MacbookPro] Desktop$ gcc -E test.c -o test.i
............
int main (void)
{
printf("Hello Word,%f\n",3.14);
return 0;
}
(2) 編譯: 不同平臺全用的彙編語言是不一樣的。編譯將高級語言編譯成彙編語言。
[wlsh@wlsh-MacbookPro] Desktop$ gcc -S test.i -o test.s
[wlsh@wlsh-MacbookPro] Desktop$ gcc -S test.c -o test.s
[wlsh@wlsh-MacbookPro] Desktop$ cat test.s
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 14 sdk_version 10, 14
.section __TEXT,__literal8,8byte_literals
.p2align 3 ## -- Begin function main
LCPI0_0:
.quad 4614253070214989087 ## double 3.1400000000000001
.section __TEXT,__text,regular,pure_instructions
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movsd LCPI0_0(%rip), %xmm0 ## xmm0 = mem[0],zero
movl $0, -4(%rbp)
leaq L_.str(%rip), %rdi
movb $1, %al
callq _printf
xorl %ecx, %ecx
movl %eax, -8(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "Hello Word,%f\n"
.subsections_via_symbols
(3) 彙編:將彙編語言翻譯成二進制的目標代碼。
[wlsh@wlsh-MacbookPro] Desktop$ gcc -c test.s -o test.o
[wlsh@wlsh-MacbookPro] Desktop$ file test.o
test.o: Mach-O 64-bit object x86_64
[wlsh@wlsh-MacbookPro] Desktop$ ./test.o
bash: ./test.o: Permission denied
(4) 鏈接:包含各函數庫的入口,得到可執行代碼。 g
[wlsh@wlsh-MacbookPro] Desktop$ gcc -o test test.o
[wlsh@wlsh-MacbookPro] Desktop$ ./test
Hello Word,3.140000
文件後綴
.C or .cc | c++代碼 |
.h | 頭文件 |
.i |
預處理代碼 |
.s | 彙編代碼 |
.o | 二進制目標代碼 |
a.out | 可執行代碼(默認) |
gcc工具選項
-O0/1/2/3 | 優化,使代碼性能更優,去掉冗餘代碼,由工具自動生成 |
-g0/1/2/3 |
產生調試信息,gdb需要 |
-Wall -Werror | 提示警告 |
-D | 在命令中定義宏 |
-I | 指定頭文件位置 |
-std=c99 |
使用c99 |
#include <stdio.h>
#define pi 3.14
int main(void){
printf("Hello World. %f",pi);
for(int i = 0;i < 10; i++)
return 0;
}
[wlsh@wlsh-MacbookPro] Desktop$ gcc -o test test.c -std=c89 -Wall
test.c:6:6: warning: GCC does not allow variable declarations in for loop initializers before C99 [-Wgcc-compat]
for(int i = 0;i < 10; i++)
^
1 warning generated.
[wlsh@wlsh-MacbookPro] Desktop$ gcc -o test test.c -std=c99 -Wall
[wlsh@wlsh-MacbookPro] Desktop$ ./test
Hello World. 3.140000
gdb調試器
要解決bug,使用gdb調試器。
-g | 把編譯加到gcc編譯過程 |
l |
list.列出各行編號 |
n | next.下一執行 |
c | continue下一個斷電 |
b | 行號.在相應的行設置斷點 |
p | 變量名.打印這個變量的值 |
筆者的系統是macOS Mojave , 默認是lldb,gdb未安裝,暫且連接到遠程的ubuntu系統實驗
[wlsh@wlsh-MacbookPro] ~$ ssh -p 22 [email protected]
[email protected]'s password:
Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.8.0-36-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
37 packages can be updated.
2 updates are security updates.
*** System restart required ***
wlsh@wlsh-ThinkStation:~/Documents$ cat bug.c
#include <stdio.h>
#include <stdlib.h>
static char buff [256];
static char* string;
int main(){
printf("Please input a string: ");
gets(string);
printf("\n Your string is: %s \n",string);
}
wlsh@wlsh-ThinkStation:~/Documents$ gcc -o bug bug.c -g
bug.c: In function ‘main’:
bug.c:8:9: warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration]
gets(string);
^
/tmp/cceKpgp9.o: In function `main':
/home/wlsh/Documents/bug.c:8: warning: the `gets' function is dangerous and should not be used.
wlsh@wlsh-ThinkStation:~/Documents$ ./bug
Please input a string: helloword
Segmentation fault (core dumped)
gdb調試試下
wlsh@wlsh-ThinkStation:~/Documents$ gdb ./bug
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./bug...done.
(gdb) l
1 #include <stdlib.h>
2 #include <stdio.h>
3 static char buff [256];
4 static char* string;
5 int main()
6 {
7 printf("Please input a string: ");
8 gets(string);
9 printf("\n The string is: %s\n",string);
10 }
(gdb) b 7
Breakpoint 1 at 0x40056a: file bug.c, line 7.
(gdb) r
Starting program: /home/wlsh/Documents/bug
Breakpoint 1, main () at bug.c:7
7 printf("Please input a string: ");
(gdb) n
8 gets(string);
(gdb) n
Please input a string: helloworld
Program received signal SIGSEGV, Segmentation fault.
_IO_gets (buf=0x0) at iogets.c:53
53 iogets.c: No such file or directory.
(gdb) exit
Undefined command: "exit". Try "help".
(gdb) quit
A debugging session is active.
Inferior 1 [process 29040] will be killed.
Quit anyway? (y or n) y
wlsh@wlsh-ThinkStation:~/Documents$
錯誤二:gets()報錯,函數執行需要一個棧空間,但這個棧空間容量是有限的,而且棧裏存放了函數返回的地址。gets()函數在獲取輸入時,如果無限輸入會造成棧空間溢出,在程序返回時,不能正常的找到返回地址,程序將發生不可預測行爲。
參考網址:https://cloud.tencent.com/developer/article/1353797
bug.c:8:9: warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration]
gets(string);
^
/tmp/cceKpgp9.o: In function `main':
解決方法:fgets。通過man。fgets會認爲用戶輸入的回車也是字符串的一部分內容。 fgets是安全的,不會因爲用戶惡意的輸入過長的字符串導致溢出。因爲它只接受它能存的最大的字符數,其餘的舍掉!
wlsh@wlsh-ThinkStation:~/Documents$ man gets
#include <stdio.h>
char *gets(char *s);
wlsh@wlsh-ThinkStation:~/Documents$ man fgets
int fgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
wlsh@wlsh-ThinkStation:~/Documents$ cat test.c
#include <stdio.h>
int main ( ) {
char name[20] = { 0 };
fgets(name, sizeof(name), stdin);
printf("%s", name);
return 0;
}
wlsh@wlsh-ThinkStation:~/Documents$ gcc -o test test.c -g
wlsh@wlsh-ThinkStation:~/Documents$ ./test
helloworld
helloworld
頭文庫、庫文件
printf 這是一個庫函數。加快開發的進度。
頭文件:.h,裏面是函數及變量的聲明。
系統定義的頭文件 |
Linux下默認的頭文件搜索路徑 /usr/include #include <stdio.h> |
用戶定義的頭文件 |
程序的當前路徑 #include "myinclude.h" |
庫文件:/lib 是別人已經開發好的函數編譯的目標文件,可重定位。
例如:C庫函數
wlsh@wlsh-ThinkStation:~$ ls /lib/i386-linux-gnu/libc.so.6 -l
lrwxrwxrwx 1 root root 12 Feb 6 03:57 /lib/i386-linux-gnu/libc.so.6 -> libc-2.23.so
如何查看一個可執行代碼鏈接的庫
wlsh@wlsh-ThinkStation:~/Documents$ ldd ./test
linux-vdso.so.1 => (0x00007ffd18379000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9ef8204000)
/lib64/ld-linux-x86-64.so.2 (0x000055fa25f45000)
編譯時默認鏈接 c 庫,如果要全用其它的庫,編譯時要用-l
gcc o test test.c –lm -lc 鏈接math庫
系統限制(3.4.3)
本身平臺的類型。32 位的平臺?64 位平臺。
理解 數據類型的限制:limit.h float.h
wlsh@wlsh-ThinkStation:~$ cat /usr/include/limits.h
/* Number of bits in a `char'. */
# define CHAR_BIT 8
/* Minimum and maximum values a `signed char' can hold. */
# define SCHAR_MIN (-128)
# define SCHAR_MAX 127
....
系統本身的限制:系統的資源是有限制,不可能無限制的申請資源。
命令行:ulimit 來修改和獲取。
編程時:getrlimit 函數來獲取,setrlimit 來設置系統的限制。
wlsh@wlsh-ThinkStation:~$ man getrlimit
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
struct rlimit *old_limit);
struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
RLIMIT_CORE |
core 文件的最大字節數。core 文件是系統某個進程出現異常退出時,系統爲其保 存的上下文信息,在調試程序時經常要用。 |
RLIMIT_CPU |
CPU 時間的最大值(秒) |
RLIMIT_DATA |
一個進程數據段的最大字節數。 |
RLIMIT_FSIZE |
可創建文件的大小最大值。 |
RLIMIT_NOFILE |
每個進程可以打開的文件的個數。 |
RLIMIT_STACK |
進程棧空間的最大值。使系統不會自動的動態修改這個限制。 |
RLIMIT_VMEM |
虛擬地址空間的最大值。 |
RLIMIT_AS |
系統進程可用內存空間最大值。 |
RLIMIT_FSIZE FCHR_MAX |
|
RLIMIT_NOFILE OPEN_MAX |
命令行參數
-l是選項、/home是參數
wlsh@wlsh-ThinkStation:~$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=d0bc0fb9b3f60f72bbad3c5a1d24c9e2a1fde775, stripped
wlsh@wlsh-ThinkStation:~$ ls -l /home/
total 4
drwxr-xr-x 37 wlsh wlsh 4096 May 10 10:48 wlsh
main()函數是有參數的,而且有返回值,其中argc是參數的個數、argv[]是指針數組,粗放是具體的參數列表。相當於main()的形參。
int main (int argc,char* argv[] ) {}
int main (int argc,char** argv ) {}
wlsh@wlsh-ThinkStation:~/Documents$ cat test.c
#include <stdio.h>
int main (int argc,char* argv[] ) {
int i;
for(i=0; i<argc;i++){
printf("argv[%d] = %s\n",i,argv[i]);
}
return 0;
}
wlsh@wlsh-ThinkStation:~/Documents$ ./test -l -a -i
argv[0] = ./test
argv[1] = -l
argv[2] = -a
argv[3] = -i
程序的執行根據命令行輸入的參數發生變化。
如果命令行的選項很多,怎麼來提取這些選項呢?
不需要程序員來知道命令行選項的順序。ls –l –a –i 與 ls –l –i –a 選項應該是完全一樣的。因此,在 main 函數應該首先提取命令行參數列表。
長選項和短選項的區別 :短選項只有一個字符,長選項是一個字符串。
系統函數 man 2 foo
標準庫 man 3 bar
實際上,在命令行中,可以支持這樣命令輸入的信息:
選項:一個選項一般完成不同的功能的操作。
參數:在執行相應選項功能操作時輸入的信息。
具體 getopt 怎麼來解釋我們的選項和參數。
每成功執行一次,將返回當前的一個選項。並且
extern char *optarg; //指向下一個要掃描的參數。
extern int optind; //索引爲下一個要處理的指針的下標。
extern int optopt; //用於存儲可能的錯誤或不可知的信息
extern int opterr; //opterr== 0,不將錯誤輸出的標準錯誤輸出設備。
#include <unistd.h>
int getopt(int argc, char * const argv[],
const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
#include <getopt.h>
int getopt_long(int argc, char * const argv[],
const char *optstring,
const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char * const argv[],
const char *optstring,
const struct option *longopts, int *longindex);
getopt(): _POSIX_C_SOURCE >= 2 || _XOPEN_SOURCE
getopt_long(), getopt_long_only(): _GNU_SOURCE
爲了識別命令行的輸入信息,第 1 個參數爲 main 的 argc,第 2 個參數爲 main 提供的 argv[],getopt 函數第三個參數約定:
(1)如果就是一個字符,表示某個選項。
(2)如果一個字符後有 1 個冒號,表示選項後面一定要跟一個參數。參數可以緊跟選項或者與選 項相隔一個空格。
(3)如果一個字符後有 2 個冒號,表示選項後面可有有一個參數,也可以沒有參數,在選項後的 參數一定不能跟它以空格間隔。
“ab:c::d::”
a 後面 沒有冒號,是一個選項。
b 後面有冒號,其後的內容一定要有理解爲參數。
c 和 d 雙冒號,其後的內容可以有,也可以沒有,但如果有,則這個參數一定堅挨着。 因此如下:
./getopt –a –b host –chello –d world
短選項示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int result;
opterr = 0;
while( (result = getopt(argc, argv, "ab:c::")) != -1 )
{
switch(result)
{
case 'a':
printf("option=a, optopt=%c, optarg=%s\n", optopt, optarg);
break;
case 'b':
printf("option=b, optopt=%c, optarg=%s\n", optopt, optarg);
break;
case 'c':
printf("option=c, optopt=%c, optarg=%s\n", optopt, optarg);
break;
case '?':
printf("result=?, optopt=%c, optarg=%s\n", optopt, optarg);
break;
default:
printf("default, result=%c\n",result);
break;
}
printf("argv[%d]=%s\n", optind, argv[optind]);
}
printf("result=-1, optind=%d\n", optind);
for(result = optind; result < argc; result++)
printf("-----argv[%d]=%s\n", result, argv[result]);
for(result = 1; result < argc; result++)
printf("\nat the end-----argv[%d]=%s\n", result, argv[result]);
return 0;
}
wlsh@wlsh-ThinkStation:~/Documents$ gcc -o getopt_exp getopt_exp.c
wlsh@wlsh-ThinkStation:~/Documents$ ./getopt_exp -a -b host -chello
option=a, optopt=, optarg=(null)
argv[2]=-b
option=b, optopt=, optarg=host
argv[4]=-chello
option=c, optopt=, optarg=hello
argv[5]=(null)
result=-1, optind=5
at the end-----argv[1]=-a
at the end-----argv[2]=-b
at the end-----argv[3]=host
at the end-----argv[4]=-chello
wlsh@wlsh-ThinkStation:~/Documents$ ./getopt_exp -a -b host -chello -d world
option=a, optopt=, optarg=(null)
argv[2]=-b
option=b, optopt=, optarg=host
argv[4]=-chello
option=c, optopt=, optarg=hello
argv[5]=-d
result=?, optopt=d, optarg=(null)
argv[6]=world
result=-1, optind=6
-----argv[6]=world
at the end-----argv[1]=-a
at the end-----argv[2]=-b
at the end-----argv[3]=host
at the end-----argv[4]=-chello
at the end-----argv[5]=-d
at the end-----argv[6]=world
例如,期望當前進程支持以下選項方式
-h |
--help |
-o filename | -output filename |
-v | --version |
extern char *optarg;
extern int optind, opterr, optopt;
#include <getopt.h>
int getopt_long(int argc, char * const argv[],
const char *optstring,
const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char * const argv[],
const char *optstring,//當前支持的短選項列表,同getopt
const struct option *longopts, //長選項列表信息
int *longindex);
struct option{
const char* name; //長選項名
int has_arg; //是否有參數
int *flag;
int val; //返回值,短選項值
}
長選項示例
1