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

目录

   一.用gcc生成静态库和动态库

实例一

1.创建目录

2.gcc编译得到.o文件

3.创建静态库

4.使用静态库

5.验证静态库的使用特点

6.创建动态库

7.使用动态库

8.静态库与动态库比较

实例二

1.创建文件

2.静态库.a文件的生成与使用

3.动态库的使用

实例三(第一次作业改编)

1.创建文件

2.创建静态库文件

3.创建动态库文件

4.静态库与动态库的生成文件比较

二、GCC不是一个人在战斗

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

总结


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

部分工具

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
水资源是人类社会的宝贵财富,在生活、工农业生产中是不可缺少的。随着世界人口的增长及工农业生产的发展,需水量也在日益增长,水已经变得比以往任何时候都要珍贵。但是,由于人类的生产和生活,导致水体的污染,水质恶化,使有限的水资源更加紧张。长期以来,油类物质(石油类物质和动植物油)一直是水和土壤中的重要污染源。它不仅对人的身体健康带来极大危害,而且使水质恶化,严重破坏水体生态平衡。因此各国都加强了油类物质对水体和土壤的污染的治理。对于水中油含量的检测,我国处于落后阶段,与国际先进水平存在差距,所以难以满足当今技术水平的要求。为了取得具有代表性的正确数据,使分析数据具有与现代测试技术水平相应的准确性和先进性,不断提高分析成果的可比性和应用效果,检测的方法和仪器是非常重要的。只有保证了这两方面才能保证快速和准确地测量出水中油类污染物含量,以达到保护和治理水污染的目的。开展水中油污染检测方法、技术和检测设备的研究,是提高水污染检测的一条重要措施。通过本课题的研究,探索出一套适合我国国情的水质污染现场检测技术和检测设备,具有广泛的应用前景和科学研究价值。 本课题针对我国水体的油污染,探索一套检测油污染的可行方案和方法,利用非分散红外光度法技术,开发研制具有自主知识产权的适合国情的适于野外便携式的测油仪。利用此仪器,可以检测出被测水样中亚甲基、甲基物质和动植物油脂的污染物含量,为我国众多的环境检测站点监测水体的油污染状况提供依据。
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值