01 開發工具與開發平臺

本系列博客是根據視頻:http://watchmen.cn/forum.php?mod=forumdisplay&fid=50做的筆記

目錄

代碼編譯的四個步驟

文件後綴

gcc工具選項

gdb調試器

頭文庫、庫文件

系統限制(3.4.3)

命令行參數


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
/usr/local/include
/usr/target/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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章