全局变量和局部变量_说明局部变量在哪个文件中声明,在哪个文件中给全局变量中赋初值,并举例说明一个全-程序员宅基地

技术标签: stm32  嵌入式学习  

一、C语言由四种地方可以定义变量

在函数外部定义的是全局变量(这里的函数包括main函数)

在头文件中定义的是全局变量

在函数或语句块内部定义的是局部变量

函数的参数是该函数的局部变量

全局变量,在定义位置之后的任意函数都能访问.

二、区别

存储的区别
全局变量存储在一个程序的data段中的静态数据区

局部变量存储在一个程序的data段中的栈区(stack),我们每定义一个局部变量,栈就会分配一块空间用来存储我们定义的局部变量

 

作用域的区别

作用域是指程序中被定义的变量存在(或生效)的区域,超过该区域变量就不能访问

局部变量的作用域仅限于定义这个变量的函数内部,在一个函数内部定义了,就不能在其它函数内部使用这个变量

#include<stdio.h>
void swap()
{
	int a = 10;  //在swap函数内定义一个局部变量a
}
int main()
{
	swap();
	printf("%d", a);//在主函数内部是不能使用的
 
}

全局变量的作用域是整个源程序,也就是整个工程文件,也就是谁说,定义了一个全局变量,这个变量不仅可以在多个函数内部使用,还可以在同一工程中的其它文件中使用!!(这一点太重要了)

#include<stdio.h>
int a = 10;  //定义一个全局变量a
void swap()
{
	printf("%d", a);
}
int main()
{
	swap();
	printf("%d", a);//在主函数内部是不能使用的
 
}


一. 内部函数&外部函数

函数本质上是全局的,因为定义一个函数的目的就是要被另外的函数调用;如果不加声明的话,一个文件中的函数既可以被本源文件中其他函数调用,也可以被其他源文件中的函数调用,但是也可以指定某些函数不能被其他源文件调用;根据函数能否被其他源文件调用,将函数区分为 内部函数 和 外部函数

1、内部函数

如果一个函数只能被本源文件中其他函数调用,则称为 内部函数;在定义内部函数时,在函数名和函数类型的前面加上 static ,即:

static 类型名 函数名(形参表);
例如:
static int fun(int a, int b);

内部函数又称为 静态函数;使用内部函数,可以使函数的作用域只局限于所在文件,这样即使在不同的文件有同名的函数也互不干扰;

通常把只能由本源文件使用的函数放在文件的开头,前面都冠以 static 使之局部化,其他文件不能引用

2、外部函数

如果在定义函数时,在函数的首部加上关键字 extern ,则此函数是外部函数

extern int fun(int a, int b);

这样函数 fun 就可以为其他文件调用;C 语言规定,如果在定义函数时省略 extern ,则默认为外部函数,但是在调用此函数的其他源文件中,需要对此函数进行声明(extern int max(int a, int b);)!!在对此函数作声明时,要加关键字 extern ,表示该函数 是在其他文件中定义的外部函数;

///file1.c
#include <stdio.h>
 
int main(void)
{
	extern int max(int a, int b);//函数声明
	int c = 5, d = 10;
	printf("max = %d\n", max(c, d));
	return 0;
}
 
///file2.c
int max(int a, int b)
{
	return(a>b?a:b);
}

在file1.c 中声明函数 max 是外部的,此时就没有问题了;实际上,使用 extern 声明就能够在本文件调用其他文件中定义的函数,或者说把该函数的作用域扩展到本文件

由于函数在本质上是外部的,在程序中经常要调用其他文件中的外部函数,为了方便,C语言允许在声明函数时省写 extern,如上面的在 max 前省略 extern 也是可以的;(但还是要声明 不能直接调用)


什么时候需要加声明?

// test1.c
int
fun (int num)
{
    return num * 2;
}

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}
// test1.c
int fun (int num);  //加上了声明

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

int
fun (int num)
{
    return num * 2;
}

对比1、2两段代码    把子函数 fun 的实现放在 main 函数的后面并且在 mian 函数之前加上 fun 函数的声明。

当解析到 main 函数中的 fun 函数调用时,编译程序就会向前寻找 fun 函数的实现或声明,当发现 fun 函数的声明时,编译程序就会知道 fun 函数的实现在主函数的之后,编译程序便会继续正常编译。
在一些编译器编译程序时,就算在 main 函数之后实现 fun 函数,main 函数之前不加 fun 函数的声明,也不会有出错信息,但正确的语法是要求加上的。


为什么写代码需要头文件这种东西?

引入头文件
上面的程序结构安排使人感觉即利于修改又便于维护,但仔细一分析还是发现一个问题。
问题:当我写了一个 test2.c 程序文件,里面的 main 函数也需要调用 fun 函数时,我需要在 test2.c 程序文件在加入 fun 函数的实现。当我有许多 test 程序文件,里面的 main 函数都需要调用 fun 函数时,我需要在每一个 test 程序文件中都加入 fun 函数的实现。这样的工作重复而又无意义。
解决这个问题最简单的方法就是引入头文件。我可以写一个 test.h 的自定义头文件,把 fun 函数的实现放进去,每一个调用 fun 函数的 test 程序文件只需要引入头文件 test.h 即可
这里需要解释一下编译第一阶段——预处  理。预处理器(cpp)根据以字节#开头的命令,修改原始的C程序。比如 test.c 中第1行的 #include “test.h” 命令告诉预处理器读取自定义头文件 test.h 的内容,并把它直接插入到程序文本中。结果就得到了另一个C程序,通常是以 .i 作为文件扩展名。这里所说的插入是直接把整个系统头文件copy到程序文本#include的位置。并且加上一些标志信息。
下面代码展示:

// test.h
#ifndef TEST_H
#define TEST_H
int
fun (int num)
{
    return num * 2;
}
#endif
// test.c
#include "test.h"

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

经过预处理后,得到 test.i 程序文件。

// test.i
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 1 "test.h" 1
int
fun (int num)
{
	return num * 2;
}
# 2 "test.c" 2

int
main (void)
{
	int local = 10;
	int ret = local + fun (local);
	return ret;
}

可以发现 test.h 自定义头文件被复制到了原先 #include 的位置,并且加上了一些标志信息(那些绿色的信息)。因为我们的 #include 在主函数之前,所以在 test.i 程序文件中,fun 函数的实现在 main 函数的前面,因此我们不需要任何函数声明。

这样做很快就会发现另一个问题。
问题:当我因为需要修改 main 函数后,在重新编译的过程中,因为 test.c 文件中有 #include “test.h”,预处理后得到 test.i 预处理文件,fun 函数的实现被复制到 test.i 文件中,fun 函数也需要重新编译一遍。当 fun 函数的实现较为简单时,这样做没有太大的影响,但是当 fun 函数的实现较为复杂时,这样做十分不利于调试和维护。
解决这个问题最好的方法就是引入 fun.c 文件,把 fun 函数的实现放在 fun.c 文件中,在 test.h 自定义头文件中放入 fun 函数的声明。代码如下:

// fun.c
int
fun (int num)
{
    return num * 2;
}
// test.h
#ifndef TEST_H
#define TEST_H
extern int fun (int num);
#endif
// test.c
#include "test.h"

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

这样做我们可以对 test.c 和 fun.c 这两个文件分开预处理、编译、汇编。当得到 fun.o 和 test.o 两个可重定位目标文件时,用链接器将这两个文件连接成可执行目标文件 test 。

gcc -E test.c -o test.i  // 预处理
gcc -S test.i -o test.s  // 编译
gcc -c test.s -o test.o  // 汇编
gcc -E fun.c -o fun.i    // 预处理
gcc -S fun.i -o fun.s    // 编译
gcc -c fun.s -o fun.o    // 汇编
gcc fun.o test.o -o test // 链接

当我们需要修改 main 函数时,只需要对 test.c 文件重新预处理、编译、汇编。得到 fun.o 后重新与 test.o 链接一下就可以得到可执行目标文件 test 。
因为 main 函数中调用了 fun 函数,而 fun 函数的实现又没有与 main 函数在同一文件中,所以需要在 test.c 文件中 main 函数之前加上 fun 函数的外部声明,以此来表明 fun 函数的实现在另一个文件中。而 fun 函数的声明在自定义头文件 test.h 中,所以在 test.c 文件中 mian 函数之前加上 #include “test.h” 。

查看预处理 test.c 后得到的 test.i 文件可以检验这个观点。

// test.i    
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 1 "test.h" 1


extern int fun (int num);//预处理把test.h里的东西加载进来 test.h里有函数声明 所以这里把声明加载进来
# 2 "test.c" 2

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

如果在 test.c 文件中没有 #include “test.h” ,在编译过程中便会出现警告信息:warning : implicit declaration of function ‘fun’


联系系统头文件

如果查看过系统头文件,便可以发现:在系统头文件中都是一些函数声明和宏定义,而没有任何函数的实现。我们调用库函数时必须要包含特定的系统头文件,就是因为系统头文件中有我们使用库函数的外部声明
拿 hello.c 程序文件来举例,代码如下:

#include <stdio.h>

int main()
{
	printf("hello, world\n");
	return 0;
}

因为我们的程序文件中调用了库函数 printf ,所以需要包含头文件 #include <stdio.h> ,因为系统头文件 stdio.h 中有 printf 函数的声明,预处理得到 hello.i 文件后在 main 函数之前便会有 printf 函数的外部声明,接下来的编译过程便不会出现警告信息。当经过汇编阶段得到 hello.o 可重定位目标文件后,编译器会自动链接 printf.o 可重定位文件,最终生成可执行目标文件


当我们修改 main 函数后重新编译时,编译器会把 hello.c 程序文件重新预处理、编译、汇编得到 hello.o 可重定位目标文件,然后与 printf.o 链接生成可执行目标文件。编译器当然不会重新预处理、编译、汇编 printf.c 文件。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42747105/article/details/129840069

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法