每日一题(十四)
11.1 gcc输出文件类型
预编译
编译
汇编
链接
gdb:
11.2 符号中的空格
符号的中间一般不要加空格符、制表符、换行符,不然会引起歧义,比如---
符号,空格的位置不同,运算结果也不同:
对于a --- b
,编译器从左往右看,先读入一个-
,再接着读入一个-
,到这里编译器就认为这是一个自减运算符,再接着向右读入-
,编译器认为---
是先执行自减–-
操作,然后执行减运算-
的,所以a --- b
在编译器看来就是a -- - b
运算,得到结果为-1。
但是如果在第一个-
之后就加上空格,即a - -- b
,编译器就会认为第一个减号之后是程序员专门隔开的,所以先执行–-b
再执行a - b
,得到0。
再比如注释符/**/
,如果遇到除数是指针指向的数据,可能会引起歧义:
如果之间在/
后面加上*p
,就会让编译器认为这是注释/**/
的开始,所以加上空格就会明确了。
所以,以后在使用比较复杂的运算符组合的时候,切记要用空格来表达逻辑。
11.3 单双引号的区别
简单来说,单引号‘’
括起来的字符代表一个整数,就是该字符的ASCII码值;
双引号“”
括起来的字符串代表的是一个指向无名数组起始字符的指针,该无名数组被双引号包括的字符串以及一个\0
字符初始化。
11.4 函数类型转换符——显示调用指定地址函数
如果我们要调出首地址为0xff位置的子函数,要想通过函数显式调用的方式,该怎么办?
通过类型转换符来进行强制类型转换,最终的语句为:(*void(*)() 0xff)();
构造这种表达式的核心思想就是:按照使用的方式来声明,也就是先了解要干什么,然后再看怎么做。
1.首先要明白C中变量的声明!!!
C中的变量声明由两部分组成:
- 变量的数据类型,比如:int、float、char等等
- 声明符,相当于变量的表达式,对声明符求值会返回一个声明中给的的数据类型的结果,比如:
char *p
中的*p
,对*p
求值会返回一个char类型的数据
比如:
char ff();
:声明符ff()的求值结果是char,代表ff()函数的返回值是一个char类型的数据,即声明了ff是一个返回值为char的函数;
float *pf;
:声明符*pf的求值结果是一个float类型的数据,即声明了pf是一个指向float的指针;
int *g();
:声明符*g()
的求值结果是一个int型的数据,()
优先级比*
高,所以可以看作*(g())
,其中g是一个函数,(g())
代表函数的返回值,*(g())
代表对函数返回值的地址求值,得到一个int数据,也就是函数返回的是一个int型的指针;int *g()
也可以写成int* g()
,表示函数g的返回值是一个int类型的指针;
int (*h)()
:声明符中先执行(*h)
,表示这是一个指针,后面(*h)()
表示这个指针指向h函数,所以表示h是一个指向返回值是int的函数,即h是一个函数指针;
2.已知函数指针,如何调用函数?
比如fp
是一个函数指针,那么调用这个函数的方法就是:(*fp)();
,也可以简写为:fp()
。
注意,不要把函数指针种指针的括号去掉,如果变为
*fp()
,就表示函数fp返回一个指针,*
操作就是取得返回值的传递的值!
3.由变量声明推出相应的 类型转换符
了解声明之后,再看一下如何得到相应的类型转换符:把声明中的变量名和末尾的分号去掉,将剩余部分用括号封装起来就可以了。
比如我需要将一个指针强制转换为指向一个返回float的函数,首先写出此种函数的声明:
float (*f)();
,然后去掉变量名和结尾分号,括号封装起来就是:(float (*)())
,表示一个指向返回值是float型的函数的指针 的类型转换符。
言归正传,要调出首地址为0xff位置的子程序,就是要一个指针指向0xff位置的子程序!!!
具体方法是先写出这种指针的类型转换符,然后通过类型转换符声明一个指针指向0xff地址,过程如下:
- 先借助声明,一个指向函数的指针:
void (*f)();
- 写出类型转换符,一个指向函数的指针的类型转换符:
(void (*)())
- 调用类型转换符创建指针指向0xff的子程序:
(*(void (*)()) 0xff)()
4.调用函数
调用函数的方式就是通过函数指针fp:(*fp)();
,也就是(*函数指针)
代表的是被调用函数的地址,这里是0xff,但是单纯的0xff是不行的,我们需要对0xff进行类型转换!!!
对0xff地址进行类型转换得到函数指针fp:(void (*)())
,使用类型转换符转换0xff地址时,fp
相当于(void (*)()) 0xff
套进去得到调用0xff函数的操作:(*(void (*)()) 0xff)();
11.5 Linux下静态库与共享库的特点
库中实际上就是已编译好的函数代码,可以被程序直接调用。
Linux下的库一般的位置在/lib
或者/usr/lib
中
静态库
静态库是复制拷贝到调用函数中的,函数运行的时候不再需要静态库,因为静态库是在链接的时候加进去的,所以当函数运行的时候,源库的改变对运行中的函数造成不影响。随之而来,当静态库升级之后,每一个调用静态库的程序都需要重新编译。
特点:
- 链接静态库的时候,会把库中的相关代码拷贝到可执行文件中
- 程序运行时不再需要静态库
- 程序运行时不需加载库,运行速度快
- 因为库中相关代码是拷贝到可执行文件中,所以占用了更多的磁盘和内存空间
- 静态库升级之后需要重新编译链接
共享库
特点:
- 共享库在链接的时候,仅仅是记录一下用到了哪个库中的哪个函数,并不复制库中相关代码
- 多个程序可以同时调用一个共享库
- 程序在运行的时候来加载共享库
- 程序体积变小,程序本身没有包含库中的代码
- 共享库升级之后,无需重新编译程序