当前位置:主页 > 软件编程 > C代码 >

C/C++的关键字之static你了解吗

时间:2022-07-27 11:17:36 | 栏目:C代码 | 点击:

C语言

隐藏

场景演示

当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。会导致符号表认为存在同名全局变量和函数发生碰撞。

场景:全局的变量/函数在.h中会在多个.cc文件中拥有且全局可见有链接问题。

image-20220221183611526

a.h

#pragma once
#include<stdio.h>
void Test()
{
  printf("I am test..\n");
}

b.c

#include"a.h"
void call()
{
    Test();
}

c.c

#include"a.h"
int main()
{
   Test();   
}

makefile

all:c
c:c.o b.o
	gcc -o $@ $^ 
c.o:c.c
	gcc -c $^ 
b.o:b.c
	gcc -c $^ 
.PHONY:clean
clean:
	rm -rf *.o c

运行结果

image-20220221183828906

此时查看b.oc.o符号表。(readelf -s xxx.o)

image-20220221183926498

image-20220221183944564

可以看到双方的.o符号表中都认为有一个GLOBAL全局函数的Test,两者汇编阶段形成的符号表此时在汇总的阶段就会产生同名全局函数冲突。

此时在两者的二进制文件里都认为各自拥有Test()函数,且都在全局。而全局函数只能有一个名字(注:重载是底层重新命名了)。虽然我们知道两个Test()是同一个,但是link的时候认为有两个同名函数实现,因此报link错。

image-20220221184323225

解决方法

声明和定义分离

养成声明和定义分离的习惯,在.h中只声明不定义。在.c文件中定义。

image-20220221184956320

a.h

#include<stdio.h>
void Test();

a.c

#include"a.h"
void Test()
{
    cout<<"I am test..."<<endl;
}

makefile

all:c
c:c.o b.o a.o
	gcc -o $@ $^ 
c.o:c.c
	gcc -c $^ 
b.o:b.c
	gcc -c $^ 
a.o:a.c
	gcc -c $^ 
.PHONY:clean
clean:
	rm -rf *.o c

image-20220221185142988

为什么此时就可以正常运行了?

依然查看符号表,可以发现b.o和c.o中此时只是给Test声明留了一个全局的NOTYPE位置。

image-20220221185244568

而在a.o中定义Test(),因此a.o中是func类型。

image-20220221185352451

最后三个.o文件链接的时候确定Test()实际在最后生成的.out文件中的虚拟内存地址。运行时加载到内存中,之后的详细过程就是linux创建进程中的事情。

image-20220221185617070

使用static关键字及缺陷

那如果我就是想要直接在.h中存放一个公共的全局的对象来供其他所有文件使用呢?使用static关键字。

a.h

#pragma once
#include<stdio.h>
static void Test()
{
  printf("I am test..\n");
}

代码结构

image-20220221185854507

此时为什么又成立呢?两者.o文件中为什么对同名的全局函数包容了呢?可以看到此时两者的符号表中仍然是func,按照场景演示中的例子,应该报错的。

image-20220221185942306

此时反汇编查看Test()函数地址。我们发现此时生成了两个test函数,不过函数地址不同。

结论:static函数作用域仅在自己所在文件,其实是编译后不同文件下的同名static函数会有不同的内部命名

不同.c文件include了static变量之后该变量只在各自包含该变量的.c中可见。

image-20220221190131277

既然生成了两份,我们就可以发现,如果是一个静态的全局变量,我们分别进行修改实际上对两个不同的变量进行修改的。如果要解决全局变量统一性访问,保证全局变量不可变即可。另外一种方式就是使用单例模式。

a.h

#pragma once
#include<stdio.h>
static int a =0;

b.h

#include"a.h"
void call();

b.c

#include"b.h"
void call()
{
   a = 1;
   printf("a=%d\n",a);
}

c.c

#include"b.h"
int main()
{
   a=2;
   printf("a=%d\n",a); 
   call();
   printf("a=%d\n",a);
}

image-20220221195458359

保持变量内容的持久

在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。

内存中位置:静态存储区,在整个程序运行期间移植存在。

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他是被显示初始化)。

作用域:全局静态变量是从定义指出开始,到文件结尾,在声明他的文件之外是不可见的。

内存位置:静态存储区

初始化:未经初始化的局部静态变量会被自动初始化为0(自动对象的值是任意的,除非他是被显示初始化)。

作用域:为局部作用域,当定义他的函数或者语句块结束时,作用域结束。但是当局部静态变量离开作用域后,并没有被销毁,依然驻留在内存中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。

如下的count变量作用域在test函数中,而生命周期是整个程序。在第一次进入test()的时候会初始化,之后进入test()就不再执行第5行代码了。

#include<stdio.h>
void test()
{
    static int count =0;
    count++;
}
int main()
{
    for(int i =0 ; i < 10 ; i++ ) test();
}

默认初始化为0

默认初始化为0:在静态存储区,内存中所有的字节默认值都是0x00。

#include <stdio.h>
int a;
int main(void)
{
    int i;
    static char str[10];
    printf("integer: %d;  string: (begin)%s(end)", a, str);
    return 0;
}

Cpp

static类成员变量

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;静态的成员变量一定要在类外进行初始化

static类成员方法

用static修饰的成员函数,称之为静态成员函数。(因为该成员变量没有this指针)

static成员函数,没有this指针,不使用对象就可以调用–>fun::。

静态成员函数可以调用非静态成员函数(/成员)吗?不行。没有this指针

非静态成员函数可以调用类的静态成员函数吗?可以

class Date
{
    public:
    	Date(int year=0,int month=1,int day=1)
        {
        }
    	void f1()
        {
        }
    	static void f2()
        {
		   f1();//没有this指针
        }
    private:
}
class Date{
public:
    	void f3()
        {
            f4();//突破类域+访问限定符就可以访问 Date::f4();/对象.f4()
            //类里面是一个整体都在类域中,类里面不受访问限定符限制
        }
    	static void f4()
        {
        }
private:
};

单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

1.如何保证全局(一个进程中)只有一个唯一的实例对象

参考只能在堆上创建对象和在栈上创建对象,禁止构造和拷贝构造及赋值。

提供一个GetInstance获取单例对象。

2.如何提供只有一个实例呢?

饿汉模式和懒汉模式。

3.使用场景

由于全局的变量在.h中会在多个.cc文件中拥有且可见容易有链接问题。而static又只能在当前文件可见。因此真要处理成全局的就使用单例模式。

具体的单例模式在特殊类设计中提及。

总结

您可能感兴趣的文章:

相关文章