linuxC 开发小结1

208 阅读4分钟

经过一段时间 工作上面关于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:

如何复现符号重定义呢? 在上述例子中,发现可以共存。

下一章:我们在讲 基于优先使用内部符号可以搞什么?