宏与预处理&函数与函数库

    xiaoxiao2025-04-09  18

    1.由源代码到可执行程序的过程: 1).源码.c->(编译)->elf可执行程序 2).源码.c->(编译)->目标文件.o->(链接)->elf可执行程序 3).源码.c->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序 4).源码.c->(预处理)->与处理后的.c/.i->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序 在gcc中gcc xx.c -c -o xx.o可以指定只编译不链接,也可以生成.o的目标文件;用gcc -E xx.c -o xx.i可以只预处理,不编译。 2.预处理:     1).常见处理对象: #include(<>与""): <>是用来包含系统提供的头文件,c编译器只会到系统指定目录去寻找头文件(例如ubuntu中的usr/include);若自己指定某个文件夹(自己写的,内有头文件),那么还是还是用<>,在编译时用-I指定 ""是编译器默认会先在当前目录下寻找相应的头文件,如果没找到然后再到系统指定目录去寻找,如果还没找到则提示文件不存在。 头文件包含的真实含义就是:在#include<xx.h>的那一行,将xx.h这个头文件的内容原地展开替换这一行#include语句。过程在预处理中进行。 注释: 删掉 条件编译: #ifdef     #if    #else   #elif     #endif 我们希望程序有多种配置,我们在源代码编写时写好了各种配置的代码,然后给个配置开关,在源代码级别去修改配置开关来让程序编译出不同的效果; #ifdef:这个后面跟一个宏,只要定义的这个宏(#define XX xx 或 #define XX都可以),就执行以下代码:#ifdef  XX   下一行代码; #if:后面跟一个条件表达式,这个表达式有意义(宏替换后要是有意义的,类似于if)(#define XX xx):#if(XX ==xx)  下一行代码; 补充:可用条件编译用来输出调试信息(#define #undef  #ifdef #endif) 宏: 1).在预处理阶段由预处理器进行替换(原封不动的替换,递归替换,多个同名最后一次为准)。 2).在定义时记得要在最外面加括号,对带参宏记得里面的每个参数也要加上括号。 例如: #define SEC_PER_YEAR  (365*24*60*60UL)                //不加UL/在括号外面加UL 都是错的            在程序中出现数字时,默认时都是int类型 #define MAX(A,B)            (((A)>(B))?(A):(B))       //三目运算符,记得里面参数要加上(); 3.带参宏 1).带参宏与函数:    a.执行宏是在预处理时原地展开,但函数是在编译时在调用处跳转到函数那里,调用后返回; b.带参宏无调用开销,效率更高,在函数体较小可用,但是没有静态类型检查;函数有调用开销,但是有静态类型检查。 2).内联函数(inline声明): 内联函数本质上是函数,所以有函数的优点(内联函数是编译器负责处理的,编译器可以帮我们做参数的静态类型检查);但是他同时也有带参宏的优点(不用调用开销,而是原地展开)。所以几乎可以这样认为:内联函数就是带了参数静态类型检查的宏。在函数内函数体很短时,用内联函数很好。

    #include<stdio.h> #define DEBUG //#undef DEBUG //作用是取消一个宏,相当于把上面的宏注释掉,要是上面没有那个宏,就当啥事没发生。 #ifdef DEBUG #define debug(x) printf(x) #else #define debug(x) #endif #define MAX(a,b) (((a)>(b))?(a):(b)) #define SEC_PER_YEAR (365*24*60*60UL) inline int inline_max(int a,int b) { if (a>b) return a; else return b; } int main(int argc,char **argv) { int i=0; i=inline_max(4,16); debug("this is a debug info\n"); printf("define_the max is %d\n",MAX(3,10)); printf("inline_the max is %d\n",i); return 0; }

    4.函数 1).函数的使用规范: 第一:遵循一定格式。函数的返回类型、函数名、参数列表等。 第二:一个函数只做一件事:函数不能太长也不宜太短,原则是一个函数只做一件事情。 第三:传参不宜过多:在ARM体系下,传参不宜超过4个。如果传参确实需要多则考虑结构体打包 第四:尽量少碰全局变量:函数最好用传参返回值来和外部交换数据,不要用全局变量。 2).函数库: 最初是几个大神把一些函数源码放在一起,组成了函数库。 然后在商业上把函数库源码只编译不链接得到了.o文件,在用ar工具归档成.a文件,这就是静态链接库文件;提供这些库和相应的头文件让他人声明和调用(定义你是不造的。) 静态(.a)链接库在用户链接最终得到可执行程序期间就把相应的库函数的代码段放进可执行程序里了,而且多个程序就算调用相同库函数也会重复嵌入,这样十分占用空间。 动态(.so)链接库在用户链接得到可执行程序期间并不加入相应库函数代码段,而是做标记,在执行期间,再把相应库函数代码段放进内存中相应位置执行,这样更优秀。 使用库函数注意:(gcc下默认用的动态链接库,-static使用静态链接库) 第一,包含相应的头文件;第二,调用库函数时注意函数原型;第三,有些库函数链接时需要额外用-lxxx来指定链接;第四,如果是动态库,要注意-L指定动态库的地址。 3).字符串处理函数: 注意:mem的参数,返回值多是void; memcpy //void *memcpy(void *dest, const void *src, size_t n);       确定src和dst不会overlap,则使用memcpy效率高 memmove // void *memmove(void *dest, const void *src, size_t n);    确定会overlap或者不确定但是有可能overlap,则使用memove比较保险 memset      //void *memset(void *s, int c, size_t n);                   用常量c来填充s所指向区域的前那个字节,用memset来初始化内存空间。 memcmp      // int memcmp(const void *s1, const void *s2, size_t n);    比较s1和s2前n个中s1>s2,返回一个正数;s1=s2返回0;s1<s2返回一个负数。 memchr      // void *memchr(const void *s, int c, size_t n);            在s指向的前n个内存区域找c是否存在 strcpy       //char *strcpy(char *dest, const char *src);                      拷贝一个字符串到另一个. strncpy      //char *strncpy(char *dest, const char *src, size_t n); strcat       //char *strcat(char *dest, const char *src);                      字符串拼接函数 strncat      //char *strncat(char *dest, const char *src, size_t n); strcmp       //int strcmp(const char *s1, const char *s2);                     看Ascll码,str1>str2,返回值 > 0;两串相等,返回0      strncmp      //int strncmp(const char *s1, const char *s2, size_t n); strdup       //char *strdup(const char *s);                                    将串拷贝到新建的位置处 ,需要free()函数搭配,头文件自然还需要malloc.h strndup      // char *strndup(const char *s, size_t n);                 在vs下无,在linux下有。 注意:下面的返回的是指针哟 strchr       //char *strchr(const char *s, int c);                             在一个串中查找给定字符的第一个匹配之处  strstr       // char *strstr(const char *haystack, const char *needle);        在串中查找指定字符串的第一次出现 strtok       //char *strtok(char *str, const char *delim);                     字符串分割,第一次用后首个参数设为NULL,查找由在第二个串中指定的分界符分隔开的单词;后一个参数使用""弄起来的,不是''。注意源字符串会被改变,只保留其第一个字符。 ······· #include<stdio.h> #include<math.h> #include<string.h> int main(int argc,char **argv) { /* double a=16,b=0; b=sqrt(a); //需要-lm才能找到.so printf("sqrt(a) is %lf\n",b); */ char str[]="badmer"; char sor[]="student"; char *p="linux"; /* strcpy(str,sor); printf("the string is %s\n",str); strcpy(str,p); printf("the string is %s\n",str); strncat(str,p,4); printf("the string is %s\n",str); char* i=strchr(str,'u'); printf("the char location is %p\n",i); */ /* i=strtok(str,','); printf("the ,char location is %d\n",i); */ /* strcpy(p,str); //不可以的哦 printf("the string is %s\n",p); */ char *q="mad,cx"; /* memset(str,0,strlen(str)); printf("the string is %s\n",str); */ memmove(str,q,strlen(str)); printf("the string is %s\n",str); char *m=strtok(str,","); if(m) printf("the char is %s\n",m); m=strtok(NULL,","); if(m) printf("the char is %s\n",m); return 0; } 一般出现n修饰的是指定个数(字节或是字符);出现i修饰的是不区分大小写。 4).数学函数 1。真正的数学运算的函数定义在:/usr/include/i386-linux-gnu/bits/mathcalls.h,使用数学库函数的时候,只需要包含math.h即可。 2.使用sqrt时粗线错误:test_func.c:(.text+0x21): undefined reference to `sqrt'                            collect2: error: ld returned 1 exit status    这是链接错误,和编译错误(假如没头文件,编译错误:warning: incompatible implicit declaration of built-in function ‘sqrt’ [enabled by default])不同。 错误原因:他说没定义,那么就是没找到库函数咯,我们加上-lm,让他到libm库中去找相应函数 C链接器工作特点:链接器只是默认的寻找几个最常用的库,如果是一些不常用的库中的函数被调用,需要程序员在链接时明确给出要扩展查找的库的名字。链接时可以用-lxxx来指示链接器去到libxxx.so中去查找这个函数。     5).制作并使用链接库: 静态链接库: (1)第一步:自己制作静态链接库 gcc fun.c -o badmer.o -c  ar -rc libbadmer.a badmer.o  首先使用gcc -c只编译不连接,生成.o文件;然后使用ar工具进行打包成.a归档文件 库名不能随便乱起,一般是lib+库名称,后缀名是.a表示是一个归档文件 注意:制作出来了静态库之后,发布时需要发布.a文件和.h文件。 (2)第二步:使用静态链接库 把.a和.h都放在我引用的文件夹下,然后在.c文件中包含库的.h,然后直接使用库函数。 第一次,编译方法:gcc test.c -o test 报错信息:test.c:(.text+0xa): undefined reference to `func' 第二次,编译方法:gcc test.c -o test -lbadmer 报错信息:/usr/bin/ld: cannot find -lbadmer collect2: error: ld returned 1 exit status 第三次,编译方法:gcc test.c -o test -lbadmer -L. 无报错,生成test,执行正确。 注意:库的名字是libbadmer.a   gcc编译是命令里是-lbadmer (3)除了ar名另外,还有个nm命令也很有用,它可以用来查看一个.a文件中都有哪些符号 动态链接库: (1)第一步:自己制作静态链接库 gcc fun.c -o badmer.o -c -fPIC gcc -o libbadmer.so badmer.o -shared  -fPIC是位置无关码,-shared是按照共享库的方式来链接。 注意:做库的人给用库的人发布库时,发布libxxx.so和xxx.h即可。 (2)第二步:使用 gcc test.c -o test -lbadmer -L. 编译成功,运行报错:error while loading shared libraries: libaston.so: cannot open shared object file: No such file or directory 没找到库,因为动态链接库是在运行时加载到内存的,固定目录没找到就错误了。 解决方法: 1).解决方法一: 将libbadmer.so放到固定目录下就可以了,这个固定目录一般是/usr/lib目录。 cp libbadmer.so /usr/lib即可 #解决方法二:使用环境变量LD_LIBRARY_PATH。操作系统在加载固定目录/usr/lib之前,会先去LD_LIBRARY_PATH这个环境变量所指定的目录下去寻找,如果找到就不用去/usr/lib下面找了,如果没找到再去/usr/lib下面找。所以解决方案就是将libaston.so所在的目录导出到环境变量LD_LIBRARY_PATH中即可。 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH/mnt/hgfs/winshare/my_work/c_test/4.6.preprocess/badmer_so/release_so$  但是这种方法和当年装交叉编译工具链时一样,重启终端就不行了. 在ubuntu中还有个解决方案三,用ldconfig     (3)ldd命令:作用是可以在一个使用了共享库的程序执行之前解析出这个程序使用了哪些共享库,并且查看这些共享库是否能被找到,能被解析(决定这个程序是否能正确执行)。
    转载请注明原文地址: https://ju.6miu.com/read-1297883.html
    最新回复(0)