目录
一.用gcc生成静态库和动态库
实例一
1.创建目录
命令说明
#mkdir test /创建新的目录
#cd test /打开目录编辑
#touch hello.h /建立新的文件在该目录下
#vim hello.h /打开编辑文件
#ls /查询该文件目录下的所有文件
hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
hello.c
#include<stdio.h>void hello(const char *name)
{printf("hello %s\n",name);}
main.c
#include“hello.h”int main()
{hello("everyone!");return 0;}
2.gcc编译得到.o文件
3.创建静态库
静态库文件命名规范:前缀为lib,加上静态库名,扩展名为.a。
4.使用静态库
在静态库中使用其中的内部的函数:需在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后再用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将这些公用函数连接到目标文件中。
①gcc -o hello main.c -L. -lmyhello
②gcc main.c libmyhello.a -o hello
③先生成main.o 文件 gcc -c main.c
再生成可执行文件 gcc -o hello main.o libmyhello.a
5.验证静态库的使用特点
在删除静态库的情况下,运行可执行文件,发现程序仍然可以正常运行,表明静态库跟执行程序没有关系。同时,说明了静态库是在程序编译的时候连接上目标代码的。
6.创建动态库
动态库文件命名规范:前缀为lib,加上动态库名,扩展名为.so。
7.使用动态库
出现错误。虽然连接时连接的时用的是当前目录,但是由于运行可执行文件时,是在usr/lib
中找库文件的,故将libmyhello.so
复制到目录usr/lib
中即可
8.静态库与动态库比较
gcc编译得到.o文件
gcc -c hello.c
创建静态库
ar -crv libmyhello.a hello.o
创建动态库
gcc -shared -fPIC -o libmyhello.so hello.o
使用库生成可执行文件
gcc -o hello main.c -L. -lmyhello
执行可执行文件
./hello
在执行可执行文件,会报一个错误,可见当静态库和动态库同时存在的时候,程序会优先使用动态库。
实例二
1.创建文件
A1.c
#include<stdio.h>
void print1(int arg)
{
printf("A1 print arg:%d\n",arg);
}
A2.c
#include<stdio.h>
void print2(char *arg)
{
printf("A2 printf arg:%s\n",arg);
}
A.h
#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif
test.c
#incldue<stdio.h>
#include"A.h"
int main()
{
print1(1);
print2(test);
return(0);
}
2.静态库.a文件的生成与使用
终端控制输入以下命令gcc -c A1.c A2.c
生成目标文件;ar crv libfile.a A1.o A2.o
生成静态库.a文件;使用.a文件创建可执行文件gcc -o test test.c libfile.a
;./test
运行可执行程序
3.动态库的使用
gcc -shared -fPIC -o libfile.so A1.o A2.o
gcc -o test test.c libfile.so
实例三(第一次作业改编)
1.创建文件
创建.o文件: 控制终端运行命令gcc -c sub1.c sub2.c
sub1.c
float x2x(int a,int b)
{
float c=0;
c=a+b;
return 0;
}
sub2.c
float x2y(int a,int b)
{
float c=0;
c=a/b;
return c;
}
sub.h
#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif
main.c
#include<stdio.h>
#incldue"sub.h"
void main()
{
int a,b;
printf("Please input the value of a:");
scanf("%d",&a);
printf("Please input the value of b:");
scanf("%d",&b);
printf("a+b=%.2f\n",x2x(a,b));
printf("a/b=%.2f\n",x2y(a,b));
}
2.创建静态库文件
ar crv libsub.a sub1.0 sub2.o
生成可执行文件gcc -o mian main.c libsub.a
3.创建动态库文件
gcc shared -fPIC -o libsub.so sub1.o sub2.o
gcc -o main main.c libsub.so
4.静态库与动态库的生成文件比较
静态库
动态库
经过比较发现静态库要比动态库要小很多,生成的可执行文件大小两者之间也存在较小的差别
二、GCC不是一个人在战斗
gcc常用命令:代码实例:
test.c
#include<stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
1.编译
gcc test.c -o test
2.实际编译
①预编译
gcc -E test.c -o test.i
部分test.i的内容:
②编译为汇编代码
gcc -S test.i -o test.s
test.s的内容:
③汇编
gcc -c test.s -o test.o
④连接
gcc test.o -o test
3.多个程序文件的编译
①gcc 源文件1 源文件2 -o 生成的可执行文件
举例:
②gcc -c 源文件1 -o 生成.o文件1
gcc -c 源文件1 -o 生成.o文件2
gcc 生成.o文件1 生成.o文件2 -o 生成的可执行文件
举例:
4.检错
gcc -pedantic test.c -o test1
-pedantic:帮助发现一些不符合ANSI/ISO C标准的代码,当出现不符合的代码,会发出警告信息
gcc -Wall test.c -o test2
-Wall:帮助发现一些不符合ANSI/ISO C标准的代码,当出现不符合的代码,gcc会发出尽可能多的警告信息
gcc -Werror test.c -o test3
-Werror:gcc会在所有产生警告的地方停止编译,迫使进行代码的修改
5.库文件连接
库文件:动态链接库(.so),静态链接库(.a)
函数库:头文件(.h),库文件(.so)
注意:头文件一般放在/usr/include目录下,库文件一般放在/usr/lib目录下
①编译
gcc -c -I /usr/include 源文件 -o 生成.o文件
②链接
gcc -L /usr/lib 动态链接库文件名 生成.o文件 -o 生成可执行文件
③强制性使用静态链接库
gcc链接时会优先使用动态链接库,想强制使用静态链接库执行在命令中加-static
gcc -L /usr/lib -static 静态链接库文件名 生成.o文件 -o 生成可执行文件
静态链接时搜索路径顺序:
a、ld会去找gcc命令中的参数-L
b、gcc的环境变量LBRARY_PATH(指定程序静态链接库文件的搜索路径)
c、内定目录/lib /usr/lib /usr/local/lib
动态链接时搜索路径顺序:
a、编译目标代码时指定的动态库搜索路径
b、环境变量LD_LIBRARY_PATH(指定程序动态链接库文件的搜索路径)
c、配置文件/etc/ld.so.conf中指定的动态库搜索路径
d、默认的动态库搜索路径/lib
e、默认的动态库搜索路径/usr/lib
gcc的合作伙伴
部分工具
- addr21line
帮助调试器在调试过程中定位对应的源代码。 - ar
用于创建静态链接库。 - ld
用于链接。 - as
用于汇编。 - ldd
查看执行文件所用到的链接库。 - size
查看执行文件中各部分的大小。 - readelf
查看ELF各个部分的内容。 - objdump
进行反汇编
三.编写一个C程序,重温全局常量、全局变量、局部变量、静态变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。1)归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析;2)加深对ARM Cortex-M/stm32F10x的存储器地址映射的理解。下图是一个Cortex-M4的存储器地址映射示意图(与Cortex-M3/stm32F10x基本相同,只存在微小差异)
#include <stdio.h>
#include <stdlib.h>
//定义全局变量
int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;
void output(int a)
{
printf("hello");
printf("%d",a);
printf("\n");
}
int main( )
{
//定义局部变量
int a=2;
static int inits_local_c=2, uninits_local_c;
int init_local_d = 1;
output(a);
char *p;
char str[10] = "lmy";
//定义常量字符串
char *var1 = "1234567890";
char *var2 = "qwertyuiop";
//动态分配
int *p1=malloc(4);
int *p2=malloc(4);
//释放
free(p1);
free(p2);
printf("栈区-变量地址\n");
printf(" a:%p\n", &a);
printf(" init_local_d:%p\n", &init_local_d);
printf(" p:%p\n", &p);
printf(" str:%p\n", str);
printf("\n堆区-动态申请地址\n");
printf(" %p\n", p1);
printf(" %p\n", p2);
printf("\n全局区-全局变量和静态变量\n");
printf("\n.bss段\n");
printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);
printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);
printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);
printf("\n.data段\n");
printf("全局外部有初值 init_global_a:%p\n", &init_global_a);
printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);
printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);
printf("\n文字常量区\n");
printf("文字常量地址 :%p\n",var1);
printf("文字常量地址 :%p\n",var2);
printf("\n代码区\n");
printf("程序区地址 :%p\n",&main);
printf("函数地址 :%p\n",&output);
return 0;
}
编译:
gedit text.c
gcc text.c -o text
./text
可以发现,Ubuntu在栈区和堆区的地址值都是从上到下增长的。
STM32(Keil)环境中的变量分配
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdlib.h>
#include "Delay.h"
#include "OLED.h"
int globalVariable1; // 全局变量1
int globalVariable2; // 全局变量2
int main(void)
{
OLED_Init();//OLED初始化
while(1)
{
int stackVariable1; // 栈变量1
int stackVariable2; // 栈变量2
int *heapVariable1 = malloc(sizeof(int)); // 堆变量1
int *heapVariable2 = malloc(sizeof(int)); // 堆变量2
static int staticVariable1; // 静态全局变量1
static int staticVariable2; // 静态全局变量2
static int staticLocalVariable1; // 静态局部变量1
static int staticLocalVariable2; // 静态局部变量2
OLED_ShowHexNum(1,1,(int)&globalVariable1,8);
OLED_ShowHexNum(2,1,(int)&globalVariable2,8);
OLED_ShowHexNum(3,1,(int)&stackVariable1,8);
OLED_ShowHexNum(4,1,(int)&stackVariable2,8);
//OLED_ShowHexNum(1,1,(int)heapVariable1,8);
//OLED_ShowHexNum(2,1,(int)heapVariable2,8);
//OLED_ShowHexNum(3,1,(int)&staticVariable1,8);
//OLED_ShowHexNum(1,1,(int)&staticLocalVariable1,8);
//OLED_ShowHexNum(2,1,(int)&staticLocalVariable2,8);
free(heapVariable1); // 释放堆变量1
free(heapVariable2); // 释放堆变量2
return 0;
}
}
在STM32下,栈区的地址存储是向下增长,堆区的地址存储却是向上增长。
STM32是一种基于ARM Cortex-M处理器的微控制器系列。这些处理器一般使用倒序堆栈(downward stack)结构,也被称为从高地址向低地址增长的堆栈。
在倒序堆栈结构下,栈指针的初始值是指向栈顶的最高地址,随着栈上的数据的入栈,栈指针向低地址方向递减。当数据从栈中弹出时,栈指针会向高地址方向递增。这种栈结构的好处是可以轻松地检测栈溢出,因为栈溢出会导致栈指针超出栈的最低地址范围。
总结
通过几个示例程序和相关资料学会了用gcc生成静态库和动态库,还有静态库.a与.so库文件的生成和使用。这次实验让我更加熟练的使用gcc编译工具也更加了解了gcc。相信在以后的学习过程中会更加轻松自得。