C语言中的static 详细分析

          google了近三页的关于C语言中static的内容,发现可用的信息很少,要么长篇大论不知所云要么在关键之处几个字略过,对于想挖掘底层原理的初学者来说参考性不是很大。所以,我这篇博文博采众家之长,把互联网上的资料整合归类,并亲手编写程序验证之。

         C语言代码是以文件为单位来组织的,在一个源程序的所有源文件中,一个外部变量(注意不是局部变量)或者函数只能在一个源程序中定义一次,如果有重复定义的话编译器就会报错。伴随着不同源文件变量和函数之间的相互引用以及相互独立的关系,产生了extern和static关键字。

        下面,详细分析一下static关键字在编写程序时有的三大类用法:

        一,static全局变量

           我们知道,一个进程在内存中的布局如图1所示:

      其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量(其他段中还有很多乱七八糟的段,暂且不表)。在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝。

     当一个进程的全局变量被声明为static之后,它的中文名叫静态全局变量。静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效,其他源文件无法访问它。所以,普通全局变量穿上static外衣后,它就变成了新娘,已心有所属,只能被定义它的源文件(新郎)中的变量或函数访问。

以下是一些示例程序

file1.h如下:

#include <stdio.h>

void printStr();

我们在file1.c中定义一个静态全局变量hello, 供file1.c中的函数printStr访问.

#include "file1.h"

static char* hello = "hello cobing!";

void printStr()
{
	printf("%s\n", hello);
}

file2.c是我们的主程序所在文件,file2.c中如果引用hello会编译出错

#include "file1.h"

int main()
{
	printStr();
	printf("%s\n", hello);
	return 0;
}

报错如下:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file2.c: In function ‘main’:
file2.c:6: 错误:‘hello’ 未声明 (在此函数内第一次使用)
file2.c:6: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
file2.c:6: 错误:所在的函数内只报告一次。)


如果我们将file2.c改为下面的形式:

#include "file1.h"

int main()
{
	printStr();
	return 0;
}

则会顺利编译连接。

运行程序后的结果如下:
[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
hello cobing!

上面的例子中,file1.c中的hello就是一个静态全局变量,它可以被同一文件中的printStr调用,但是不能被不同源文件中的file2.c调用。

 

      二,static局部变量

      普通的局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放之。

       static局部变量中文名叫静态局部变量。它与普通的局部变量比起来有如下几个区别:

           1)位置:静态局部变量被编译器放在全局存储区.data(注意:不在.bss段内,原因见3)),所以它虽然是局部的,但是在程序的整个生命周期中存在。

           2)访问权限:静态局部变量只能被其作用域内的变量或函数访问。也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数和源文件访问。

           3):静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。
以下是一些示例程序:

     file1.h的内容和上例中的相同,file1.c的内容如下:

#include "file1.h"

void printStr()
{
	int normal = 0;
	static int stat = 0;	//this is a static local var
	printf("normal = %d ---- stat = %d\n",normal, stat);
	normal++;
	stat++;
}

为了便于比较,我定义了两个变量:普通局部变量normal和静态局部变量stat,它们都被赋予初值0;

file2.c中调用file1.h:

#include "file1.h"

int main()
{
 printStr();
 printStr();
 printStr();
 printStr();
 printf("call stat in main: %d\n",stat);
 return 0;
}

这个调用会报错,因为file2.c中引用了file1.c中的静态局部变量stat,如下:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file2.c: In function ‘main’:
file2.c:9: 错误:‘stat’ 未声明 (在此函数内第一次使用)
file2.c:9: 错误:(即使在一个函数内多次出现,每个未声明的标识符在其
file2.c:9: 错误:所在的函数内只报告一次。)

编译器说stat未声明,这是因为它看不到file1.c中的stat,下面注掉这一行:

#include "file1.h"

int main()
{
	printStr();
	printStr();
	printStr();
	printStr();
//	printf("call stat in main: %d\n",stat);
	return 0;
}

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
normal = 0 ---- stat = 0
normal = 0 ---- stat = 1
normal = 0 ---- stat = 2
normal = 0 ---- stat = 3

运行如上所示。可以看出,函数每次被调用,普通局部变量都是重新分配,而静态局部变量保持上次调用的值不变。

需要注意的是由于static局部变量的这种特性,使得含静态局部变量的函数变得不可重入,即每次调用可能会产生不同的结果。这在多线程编程时可能会成为一种隐患。需要多加注意。


       三,static函数
              相信大家还记得C++面向对象编程中的private函数,私有函数只有该类的成员变量或成员函数可以访问。在C语言中,也有“private函数”,它就是接下来要说的static函数,完成面向对象编程中private函数的功能。

            当你的程序中有很多个源文件的时候,你肯定会让某个源文件只提供一些外界需要的接口,其他的函数可能是为了实现这些接口而编写,这些其他的函数你可能并不希望被外界(非本源文件)所看到,这时候就可以用static修饰这些“其他的函数”。

           所以static函数的作用域是本源文件,把它想象为面向对象中的private函数就可以了。

下面是一些示例:

file1.h如下:

#include <stdio.h>

static int called();
void printStr();

file1.c如下:

#include "file1.h"

static int called()
{
	return 6;
}
void printStr()
{
	int returnVal;
	returnVal = called();
	printf("returnVal=%d\n",returnVal);
}

file2.c中调用file1.h中声明的两个函数,此处我们故意调用called():

#include "file1.h"

int main()
{
	int val;
	val = called();
	printStr();
	return 0;
}

编译时会报错:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
file1.h:3: 警告:‘called’ 使用过但从未定义
/tmp/ccyLuBZU.o: In function `main':
file2.c:(.text+0x12): undefined reference to `called'
collect2: ld 返回 1

因为引用了file1.h中的static函数,所以file2.c中提示找不到这个函数:undefined reference to 'called'

下面修改file2.c:

#include "file1.h"

int main()
{
	printStr();
	return 0;
}

编译运行:

[liujx@server235 static]$ gcc -Wall file2.c file1.c -o file2
[liujx@server235 static]$ ./file2
returnVal=6

       static函数可以很好地解决不同原文件中函数同名的问题,因为一个源文件对于其他源文件中的static函数是不可见的。

有疏漏的地方望各位多多指教~~

 


 

  • 331
    点赞
  • 736
    收藏
    觉得还不错? 一键收藏
  • 97
    评论
词法分析器是编译器的一个重要组成部分,它负责将源代码的字符流转换成有意义的单词(token)流。在C语言,我们可以使用自动机(DFA)来实现词法分析器。 以下是一个简单的C语言词法分析器的实现: ```c #include <stdio.h> #include <ctype.h> #include <stdbool.h> typedef enum { NONE, IDENTIFIER, KEYWORD, OPERATOR, DELIMITER, CONSTANT } TokenType; typedef struct { TokenType type; char value[20]; } Token; char input[1000]; int input_len = 0; int pos = 0; bool is_keyword(const char* str) { const char* keywords[] = { "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "register", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while" }; int n = sizeof(keywords) / sizeof(keywords[0]); for (int i = 0; i < n; i++) { if (strcmp(str, keywords[i]) == 0) { return true; } } return false; } bool is_operator(char c) { const char* operators = "+-*/%=&|><!^~"; int n = strlen(operators); for (int i = 0; i < n; i++) { if (c == operators[i]) { return true; } } return false; } bool is_delimiter(char c) { const char* delimiters = "(){}[],;:"; int n = strlen(delimiters); for (int i = 0; i < n; i++) { if (c == delimiters[i]) { return true; } } return false; } bool is_constant(char* str) { int len = strlen(str); if (len == 0) { return false; } for (int i = 0; i < len; i++) { if (!isdigit(str[i])) { return false; } } return true; } Token get_next_token() { Token token; token.type = NONE; strcpy(token.value, ""); if (pos >= input_len) { return token; } while (isspace(input[pos])) { pos++; } if (isalpha(input[pos])) { int i = 0; while (isalnum(input[pos])) { token.value[i++] = input[pos++]; } token.value[i] = '\0'; if (is_keyword(token.value)) { token.type = KEYWORD; } else { token.type = IDENTIFIER; } } else if (is_operator(input[pos])) { token.type = OPERATOR; token.value[0] = input[pos++]; token.value[1] = '\0'; if (input[pos] == '=') { token.value[1] = input[pos++]; token.value[2] = '\0'; } } else if (is_delimiter(input[pos])) { token.type = DELIMITER; token.value[0] = input[pos++]; token.value[1] = '\0'; } else if (isdigit(input[pos])) { int i = 0; while (isdigit(input[pos])) { token.value[i++] = input[pos++]; } token.value[i] = '\0'; if (input[pos] == '.') { token.type = CONSTANT; token.value[i++] = input[pos++]; while (isdigit(input[pos])) { token.value[i++] = input[pos++]; } token.value[i] = '\0'; } else { token.type = CONSTANT; } } else { pos++; } return token; } int main() { printf("Enter input:\n"); fgets(input, sizeof(input), stdin); input_len = strlen(input); while (pos < input_len) { Token token = get_next_token(); if (token.type != NONE) { printf("%s: %s\n", token.value, token.type == KEYWORD ? "keyword" : token.type == IDENTIFIER ? "identifier" : token.type == OPERATOR ? "operator" : token.type == DELIMITER ? "delimiter" : token.type == CONSTANT ? "constant" : ""); } } return 0; } ``` 该程序将读取用户输入的C语言源代码,并输出词法分析结果。 代码定义了5种令牌类型:IDENTIFIER(标识符),KEYWORD(关键字),OPERATOR(运算符),DELIMITER(分隔符)和CONSTANT(常量)。 `is_keyword`函数用于判断一个字符串是否为C语言的关键字。 `is_operator`函数用于判断一个字符是否为C语言的运算符。 `is_delimiter`函数用于判断一个字符是否为C语言的分隔符。 `is_constant`函数用于判断一个字符串是否为C语言的常量。 `get_next_token`函数用于获取下一个令牌。该函数首先跳过所有的空格字符。如果当前字符是字母,则将之后的所有字母和数字都读入到字符串,然后判断该字符串是否为关键字或标识符。如果当前字符是运算符,则将该字符和之后的一个字符(如果有的话)读入到字符串,并判断该字符串为运算符。如果当前字符是分隔符,则将该字符读入到字符串,并判断该字符串为分隔符。如果当前字符是数字,则将之后的所有数字都读入到字符串,并判断该字符串是否为常量。 在`main`函数,我们使用`fgets`函数读取用户输入的源代码,并逐个获取令牌并输出。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值