C语言关键字auto与register及static专项详解

目录
  • 1.auto
  • 2.register
  • 3.static

1.auto

在解释 auto 之前,先来了解一下什么是局部变量。

在很多印象中,对局部变量的描述是:函数内定义的变量称为局部变量。并且下面这段代码也很好的解释了这句话:

#include <stdio.h>
void print()
{
	int a = 10;
	printf("%d", a);
}
int main()
{
	print();
	printf("%d", a);
	return 0;
}

显然这段代码是错的,编译器也会报错:

但事实上 “函数内部定义的变量是局部变量” 这种说法是不错的,但是不准确。我们来看这样一段代码:

#include <stdio.h>
int main()
{
	int func = 10;
	if (func)
	{
		int num = 1;
	}
	printf("%d", num);
	return 0;
}

同样编译器也会报错:

所以正确的理解应该是:在 { } 中定义的变量叫做局部变量。

那么我们顺水推舟提一个问题:局部变量与我们的关键字 auto 有什么联系?

其实在早期的C语言中,局部变量是需要用 auto 修饰的,但现在的编译器发展越来越智能,会自动识别哪个变量是局部变量。所以,现在对于局部变量的 auto 关键字都是省略的。

我们可用这段代码证明:

#include <stdio.h>
void print()
{
	auto int a = 10;
	printf("%d\n", a);
}
int main()
{
	int a = 10;
	printf("%d\n", a);
	print();
	return 0;
}

所以对于 auto 这个关键字来说,只需了解、知道就好。

2.register

千万不能把这个单词翻译成:登记、注册!正确的翻译应该是:寄存器。

那寄存器是什么?寄存器是计算机 CPU 的一组硬件,存在的本质是提高计算机的运行效率,因为寄存器不需要从内存中拿数据。也就是说把数据直接放在寄存器,直接供 CPU 计算,从而提升运行效率。

所以我们大胆猜测:register 修饰的变量,意义在于尽量把修饰的变量放入 CPU 寄存器中。

那么什么样的变量适合用 register 来修饰呢?

  • 局部变量(全局变量会长时间占用寄存器)
  • 不会被写入的变量(因为一旦写入将会写回内存,register 就没有意义了)
  • 被高频使用的变量

我们通过代码来阐述如何使用这个关键字:

#include <stdio.h>
int main()
{
	register num = 10;
	int sum = 0;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		sum += num+i;
	}
	printf("%d\n", sum);
	return 0;
}

这段代码完美符合了适合使用 register 修饰的变量的三个条件,虽然规模又亿些小,算法有亿些简单。num 变量是局部变量,并且接下来的代码没有对其进行写入,并且重复使用了 10 次。

如果我们写了 200000 个局部变量,并且所有的局部变量都用 register 来修饰,那么编译器会不会因为寄存器不够用而报错?我认为不会,因为 register 的定义是尽量把变量放入寄存器中。

3.static

对于 static 这个关键字,一般应用于多文件操作中,这是因为有利于项目维护、提供安全保证。

那么对于多文件操作不是本篇的内容,我会在另外一篇单独写一篇关于这块的内容。

那么 stactic 可以修饰的对象有三个:

  • 全局变量
  • 函数
  • 局部变量

我们先观察全局变量没有被 static 修饰的时候的效果:

我们在 test.c 文件中定义全局变量:

//test.c
int num = 10;

在 test.h 文件中对此声明:

//test.h
#pragram once
#include <stdio.h>
extern int num;

在 main.c 文件中使用此变量:

//main.c
#include "test.h"
int main()
{
	printf("%d\n", num);
	return 0;
}

运行结果:

现在我们来观察当全局变量被 static 修饰的时候会发生什么:

在 test.c 下使用 static 修饰全局变量,其他文件不变:

//test.c
static int num = 10;

运行结果:

可以看到这个程序在链接时出现了错误,即 num 变量“消失”了一样。那么这种“消失”恰恰是 static 的魅力所在。

结论一:对于 static 修饰的全局变量,该变量就变为只能在本文件中使用,不能被其他文件直接使用。

但是要注意,仅仅是不能被直接使用,我们通过间接访问,程序还是可以运行的:

我们在 test.c 中定义一个函数:

//test.c
#include "test.h"
static int num = 10;
void print()
{
	printf("%d\n", num);
}

test.h 文件中声明 print 函数:

//test.h
#pragma once
#include <stdio.h>
extern int num;
void print();

在 main.c 文件中我们这样做:

//main.c
#include "test.h"
int main()
{
	//printf("%d\n", num);
	print();
	return 0;
}

那么运行的效果:

上面这种情况我们可以把它视为 static 没有修饰 print 函数的情况。所以现在我们来讨论 static 修饰函数时候的情况:

在 test.c 文件中修饰 print 函数:

//test.c
#include "test.h"
int num = 10;
static void print()
{
	printf("%d\n", num);
}

test.h 、main.c 文件不变,运行结果为:

可以看到函数被 static 修饰的效果与 全局变量变量被 static 修饰的时候一致,那么我们不难得出下面的结论:

结论二:当 static 修饰函数时,此函数只能在本文件内使用,不能被其他文件直接访问。

同理,我们可以修改代码来达到间接访问:

我们在 test.c 中多写一个函数:

//test.c
#include "test.h"
int num = 10;
static void print()
{
	printf("%d\n", num);
}
void func()
{
	print();
}

在 test.h 中对此函数进行声明:

//test.h
#pragma once
#include <stdio.h>
extern int num;
void print();
void func();

在 main.c 中调用:

//main.c
#include "test.h"
int main()
{
	//printf("%d\n", num);
	//print();
	func();
	return 0;
}

运行结果:

现在,我们观察局部变量没有被 static 修饰的时候的一段代码:

#include <stdio.h>
void test()
{
	int i = 0;
	i++;
	printf("%d\n", i);
}
int main()
{
	for (int i = 0; i < 10; i++)
	{
		test();
	}
	return 0;
}

那么它的输出结果是:

很明显,超出了我们的预期。我们的预期是输出 1,2,3,4,5,6,7,8,9,10 。为什么输出 10 次输出的都是 1 呢?对于局部变量来说,生命周期在 { } 中定义开始,在出 { } 时结束。也就是说,在 { } 中定义的局部变量在内存中开辟了一块空间,在出 { } 时开辟的空间释放(销毁)。

那有什么办法能使空间不被释放呢?有,那就是用 static 修饰局部变量:

#include <stdio.h>
void test()
{
	static int i = 0;
	i++;
	printf("%d\n", i);
}
int main()
{
	for (int i = 0; i < 10; i++)
	{
		test();
	}
	return 0;
}

输出的结果是:

那么为什么会输出这样的结果呢?从上面的结果分析我们知道了局部变量的生命周期,我们只要改变局部变量的生命周期就能够解决问题。也就是说,static 修饰的局部变量,能够让其原本储存在栈区而让其存储到静态区,只能被定义一次(对于任何变量来说都是这样,只不过我们的重点在于改变变量的生命周期)从而达到修改生命周期的作用。

结论三:static 修饰局部变量,能够让其生命周期从局部周期变为全局周期。

到此这篇关于C语言关键字auto与register及static专项详解的文章就介绍到这了,更多相关C语言 auto register static内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言中auto,register,static,const,volatile的区别详细解析

    1)auto这个关键字用于声明变量的生存期为自动,即将不在任何类.结构.枚举.联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量.这个关键字不怎么多写,因为所有的变量默认就是auto的. (2)register这个关键字命令编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率. (3)static常见的两种用途:1>统计函数被调用的次数; 2>减少局部数组建立和赋值的开销.变量的建立和赋值是需要一定的处理器开销的,特别是数组等含有较多元素的存储类型.在一

  • C语言关键字auto与register及static专项详解

    目录 1.auto 2.register 3.static 1.auto 在解释 auto 之前,先来了解一下什么是局部变量. 在很多印象中,对局部变量的描述是:函数内定义的变量称为局部变量.并且下面这段代码也很好的解释了这句话: #include <stdio.h> void print() { int a = 10; printf("%d", a); } int main() { print(); printf("%d", a); return 0;

  • C语言关键字auto与register的深入理解

    关键字概述很多朋友看到这儿可能会有疑问,往往其它讲C语言的书籍都是从HelloWorld,数据类型开始C语言学习的,为什么我们要从C语言的关键字开始呢?关于这点,我有两点需要说明:本章节面向的读者对象是有一定的C语言基础知识的朋友(至少应该学习过大学里的C语言程序设计等类似的课程)本章节结合了作者多年嵌入式工作.研究.教学经验而作,由计算机底层硬件到上层软件设计融会贯通,中间有大量的深入浅出的示例 在我对C语言进行培训的时候,往往就是从C语言的关键字入手,因为C语言的关键字蕴含了C语言的全部的词

  • Java语言面向对象编程思想之类与对象实例详解

    在初学者学Java的时候,面向对象很难让人搞懂,那么今天小编就来为大家把这个思想来为大家用极为简单的方法理解吧. 首先我们来简单的阐述面向对象的思想. 面向对象: 官方的语言很抽象,我们把官方的解释和定义抛开.想想,自己有什么,对!!我们自己有手脚眼口鼻等一系列的器官.来把自己所具有的器官就可以看作我们的属性,自己是不是可以喜怒哀乐和嬉笑怒骂,这些是不是我们的行为,那么自己的具有的属性加自己有的行为就称为一个对象. 注意!!我们自己,一个个体是一个对象,因为,你是你,我是我,我们虽然有相同的,但

  • Go语言基础语法之结构体及方法详解

    结构体类型可以用来保存不同类型的数据,也可以通过方法的形式来声明它的行为.本文将介绍go语言中的结构体和方法,以及"继承"的实现方法. 结构体类型 结构体类型(struct)在go语言中具有重要地位,它是实现go语言面向对象编程的重要工具.go语言中没有类的概念,可以使用结构体实现类似的功能,传统的OOP(Object-Oriented Programming)思想中的继承在go中可以通过嵌入字段的方式实现. 结构体的声明与定义: // 使用关键字 type 和 struct 定义名字

  • C语言预处理预编译命令及宏定义详解

    目录 程序翻译环境和执行环境 翻译环境:详解编译+链接 1. 编译 - 预处理/预编译 test.c ---- test.i 2. 编译 - 编译 test.i ---- test.s 3. 编译 - 汇编 test.s ---- test.obj 4. 链接 test.obj ---- test.exe 运行环境 预处理/预编译详解 #define 定义标识符 #和## #的作用 ##的作用 命名约定 命令行定义 条件编译 常见的条件编译指令 文件包含 offsetof(宏类型,成员名字)偏移

  • Go语言基础变量的声明及初始化示例详解

    目录 一.概述 二.声明变量 三.编译器推导类型的格式[一定要赋值] 四.短变量声明并初始化 五.匿名变量--没有名字的变量 六.注意 七.案例 一.概述 变量的功能是存储用户的数据 二.声明变量 Go语言的每一个变量都拥有自己的类型,必须经过声明才能开始用 变量的声明格式: var <变量名称> [变量类型] var a int //声明一个整型类型的变量,可以保存整数数值 var b string //声明一个字符串类型的变量 var c float32 //声明一个32位浮点切片类型的变

  • Go语言基础if条件语句用法及示例详解

    目录 概述 语法 格式规则 概述 条件语句需要开发者通过指定一个或多个条件 并通过测试条件是否为 true 来决定是否执行指定语句 并在条件为 false 的情况再执行另外的语句. 语法 package main func main() { //第一种格式 if 条件表达式 { 语句1 } //第二种格式 if 初始化表达式; 条件表达式 { 语句1 } //第三种格式 if 初始化表达式; 条件表达式 { 语句1 }else{ 语句2 } //第四种格式 if 初始化表达式; 条件表达式 {

  • C语言关于自定义数据类型之枚举和联合体详解

    目录 前言 枚举 枚举类型的定义 枚举类型的优点 枚举类型的使用 枚举中需要注意的点 联合体 联合体类型的定义 联合体的特点 联合体的使用 联合体存在内存对齐 结语 前言 在C语言的自定义数据类型中,除了我们最为常用的结构体之外,还有两个比较少用的自定义数据类型,分别为枚举和联合体(也可以称为共用体). 今天,我们一起看学习一下相关的知识吧! 枚举 什么是枚举? 顾名思义,就是一一列举,把所有的情况,所有的取值,一一列举出来. 在我们生活中,有不少的东西是可以全部列举出来的. 如一个星期有七天,

  • C语言实现生成新春福字的示例详解

    目录 主要代码 字面量以及数据结构 定义一个回调函数,刷新福字 应用初始化程序 主程序 效果展示 快新年了,支付宝扫福活动又开始了,每次都要百度找福,这次不想找了,自己写一个程序生成各种字体的福字. 主要代码 字面量以及数据结构 #define FONT_DISPLAY "福" // g_fu_label中的每一个控件都是一个福字 static GtkWidget *g_fu_label[3][3]; // 记录所有的字体family typedef struct { int n_fa

  • java并发编程关键字volatile保证可见性不保证原子性详解

    目录 关于可见性 关于指令重排 volatile关键字可以说是Java虚拟机提供的最轻量级的同步机制,但对于为什么它只能保证可见性,不保证原子性,它又是如何禁用指令重排的,还有很多同学没彻底理解 相信我,坚持看完这篇文章,你将牢牢掌握一个Java核心知识点 先说它的两个作用: 保证变量在内存中对线程的可见性禁用指令重排 每个字都认识,凑在一起就麻了 这两个作用通常很不容易被我们Java开发人员正确.完整地理解,以至于许多同学不能正确地使用volatile 关于可见性 不多bb,码来 public

随机推荐