GCC背后的故事&C程序常量变量的地址分配

GCC背后的故事&C程序常量变量的地址分配

一.用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中找库文件的,故将libm

yhello.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的合作伙伴

部分工具

  1. addr21line
    帮助调试器在调试过程中定位对应的源代码。
  2. ar
    用于创建静态链接库。
  3. ld
    用于链接。
  4. as
    用于汇编。
  5. ldd
    查看执行文件所用到的链接库。
  6. size
    查看执行文件中各部分的大小。
  7. readelf
    查看ELF各个部分的内容。
  8. objdump
    进行反汇编

三.编写一个C程序,重温全局常量、全局变量、局部变量、静态变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。1)归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析;2)加深对ARM Cortex-M/stm32F10x的存储器地址映射的理解。下图是一个Cortex-M4的存储器地址映射示意图(与Cortex-M3/stm32F10x基本相同,只存在微小差异)

img

#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。相信在以后的学习过程中会更加轻松自得。

t)&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。相信在以后的学习过程中会更加轻松自得。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值