01 开发工具与开发平台

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_24990189/article/details/90039787

本系列博客是根据视频:http://watchmen.cn/forum.php?mod=forumdisplay&fid=50做的笔记

目录

代码编译的四个步骤

文件后缀

gcc工具选项

gdb调试器

头文库、库文件

系统限制(3.4.3)

命令行参数


Linux 下编辑调试工具:gcc,gdb。

扫描二维码关注公众号,回复: 6209366 查看本文章

代码编译的四个步骤

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

猜你喜欢

转载自blog.csdn.net/qq_24990189/article/details/90039787