经过一段时间 工作上面关于linux c 的开发。做下总结。
首先获取 编译链,以及 项目代码。
使用编译链进行编译 项目代码。
在使用编译链的时候,可以写Makefile脚本来进行。这里面可能涉及 env 的配置。里面无非就是export 环境变量等。例如
# 文件名称为 env
export PATH=$PATH:/usr/bin
export CC=/usr/bin/gcc
export CPP=/usr/bin/g++
export LDFLAGS="-L/usr/lib"
之后再 source env 即可。
之后在 Makefile 中就只要写$(CC) 等就可以了。例如:
#CC=/usr/bin/gcc
SRC += test.c
all:
$(CC) $(SRC) -o test
我在这个过程【编译其他平台的代码】中遇到了有些头文件找不到。 stdio.h 有问题等。下面一一来讲解。
头文件找不到的处理方式。比方说我现在引用了 其他工具链里面的 函数声明 在 linux 平台可以通过 man 某个函数 来查找 ,但是其他的就不行了。 我是这么解决的,挺好用的。
find 某个路径 -name *.h |xargs grep 某个符号
之后如果是单独的 头文件的话,可以单独放在自己的源码底下的 include 下面
如果这个时候提取出来的头文件还有问题,就要具体原因具体分析了。 比方说我提取出来后,发现系统头文件,竟然没有某个宏定义。 这时候,类似像上面的方式,找到 宏定义,然后移植到现在的头文件上面即可。
stdio.h 导致符号重定义的问题。【没错,在一些平台上面就是这么坑,但是你又不能不用这个头文件】
解决方案:自己 项目中定义一个 stdio.h 将使用到的 符号进行 声明,其他的不用管。【没错,他会优先使用内部的头文件】下面是我这边的做法:
//文件 名称 stdio.h
//里面为空内容
test.c
#include <stdio.h>
int main(int argc,char * argv[]){
printf("hello\n");
return 0;
}
使用 gcc test.c -I. 和 g++ test.c -I. 表现的是不一样的

此处 gcc & g++ 和 clang/clang++ 表现的是一致的。
在涉及多个平台的开发过程中。可能 一些系统库函数都会不同,比方说 malloc fopen 等
现在有一种方式就是采用替换的方式。例如:
// test.h 头文件
#define TXZFILE void
void*(*pmalloc)(size_t);
TXZFILE* (*my_open)(const char *path, const char *mode);
//test.c
#include "test.h"
int main(){
pmalloc = malloc;
my_open =fopen;
TXZFILE* fp =my_open("test.h","r");
if(fp==NULL){// 不要纠结这里没有关文件的操作
printf("error open\n");
}
int* a =pmalloc(sizeof(int));//
*a=3;
printf("hello %d\n",*a);
return 0;
}
上面就是替换的初版。
现在进行改进。上述 不管是文件io的操作,还是内存相关的操作,都存在一系列类似的调用。所以可以放在结构体里面进行一起赋值。
typedef struct mem_interface
{
void* (*pmalloc)(size_t);
void* (*pcalloc)(size_t nelements, size_t bytes) ;
void* (*prealloc)(void *pointer, size_t size) ;
void (*pfree)(void *pointer) ;
}MEMINTERFACE;
typedef struct io_interface
{
TXZFILE* (* pfopen )(const char *path, const char *mode);
int (* pfclose)(TXZFILE* stream);
size_t(* pfread )(void *buffer, size_t size, size_t count, TXZFILE *stream);
size_t(* pfwrite)(const void *buffer, size_t size, size_t count, TXZFILE* stream) ;
int (* pfseek )(TXZFILE *stream, long offset, int whence) ;
int (* pferror)(TXZFILE *stream);
}IOINTERFACE;
extern MEMINTERFACE memfunc;
extern IOINTERFACE iofunc;
然后再分别实现。
//mem.c
#include <stdlib.h>
#include "test.h"
MEMINTERFACE memfunc = {&malloc, &calloc, &realloc, &free};
然后在调用,就可以了
#include "test.h"
int main(){
int* a =memfunc.pmalloc(sizeof(int));
*a=3;
printf("hello %d\n",*a);
}
就可以成功调用了。其他的也同理。这样的话,你有多少个不同的调用,就用多少个不同的实现类。
#include "test.h"
#include <stdio.h>
TXZFILE* my_fopen(const char *path, const char *mode){
printf("called my_open\n");
return fopen(path,mode);
}
IOINTERFACE iofunc = {&my_fopen, &fclose, &fread, &fwrite, &fseek, &ferror};
// test.c
TXZFILE* fp =iofunc.pfopen("test.h","r");
if(fp==NULL){
printf("error open\n");
}else{
char buf[1024]={0};
size_t ret = iofunc.pfread(buf,1,sizeof(buf),fp);
printf("read from file[%s]\n",buf);
iofunc.pfclose(fp);
}
上面的名字是不是不好记。是的,那我们统一改成我们的名字吧。
差异库的兼容处理
再导入其他开源库的时候,其他库可能会导入和我们相同的 第三方开源库。比方说:cjson ssl 等
这里举个例子,进行说明:
现在的情况是:libhello.a.1 libhello.a.2 两个版本的库
libwrap1.so libwrap2.so 两个版本的动态库,分别引用不同的版本的静态库编译而成的。
client 最终会调用 两个动态库,并进行输出,接下来进行验证。
代码分别如下
//hello.h
void sayA();
void sayB();
//hello.c
#ifdef NEW_VERSION
#define HELLO_A (say new A)\n
#define HELLO_B (say new B)\n
#else
#define HELLO_A (say A)\n
#define HELLO_B (say B)\n
#endif
#define _STR(a) #a
#define STR(a) _STR(a)
void sayA(){
printf(STR(HELLO_A));
}
void sayB(){
printf(STR(HELLO_B));
}
#if 0
void main(){
sayA();
sayB();
}
#endif
//wrap1.h
void wrap1();
//wrap1.c
#include "wrap1.h"
#include "hello.h"
#include <stdio.h>
void wrap1(){
printf("wrap1\n");
sayB();
}
//wrap2.h
void wrap2();
//wrap2.c
#include <stdio.h>
#include "hello.h"
#include "wrap2.h"
void wrap2(){
printf("wrap2\n");
sayB();
}
//client1.h
#include "hello.h"
#include <stdio.h>
#include "wrap2.h"
#include "wrap1.h"
void client(){
printf("client\n");
client2();
wrap1();
sayA();
sayB();
}
int main(){
client();
return 0;
}
编译如下
gcc -c hello.c -fPIC
ar -rc libhello.a.1 hello.o
gcc -c hello.c -DNEW_VERSION -fPIC
ar -rc libhello.a.2 hello.o
cp libhello.a.1 libhello.a
gcc -shared wrap1.c -L . -Wl,--whole-archive -Wl,-Bstatic -lhello -Wl,-Bdynamic -Wl,--no-whole-archive -o libwrap1.so
cp libhello.a.2 libhello.a
gcc -shared wrap2.c -L . -Wl,--whole-archive -Wl,-Bstatic -lhello -Wl,-Bdynamic -Wl,--no-whole-archive -o libwrap2.so
上述的结果如下:
gcc client1.c -L. -lwrap2 -lwrap1 -o test
# 执行的结果如下
txz@debian9-64-Desktop:~/test/testC$ ./test
client
wrap2
(say new B)
wrap1
(say new B)
(say new A)
(say new B)
gcc client1.c -L. -lwrap1 -lwrap2 -o test
txz@debian9-64-Desktop:~/test/testC$ ./test
client
wrap2
(say B)
wrap1
(say B)
(say A)
(say B)
通过这里的例子可以看出连接是和顺序相关的,在前面的会覆盖后面的。wrap2 为 新版本的实现。
稍微修改一下编译参数
gcc -shared wrap2.c -L . -Wl,--whole-archive -Wl,-Bstatic -lhello -Wl,-Bdynamic -Wl,--no-whole-archive -o libwrap2.so -Wl,-Bsymbolic
优先使用内部符号
再看打印:
gcc client1.c -L. -lwrap1 -lwrap2 -o test
txz@debian9-64-Desktop:~/test/testC$ ./test
client
wrap2
(say new B)
wrap1
(say B)
(say A)
(say B)
FAQ:
如何复现符号重定义呢? 在上述例子中,发现可以共存。
下一章:我们在讲 基于优先使用内部符号可以搞什么?