自学内容网 自学内容网

[iOS]static、extern、const关键字比较

[iOS]static、extern、const关键字比较

介绍这三个关键字之前先补一下课

全局区地址如何分配

先来一段代码
不难看出下面的clA bssA bssStr1属于未初始化的全局变量和静态变量,在BSS段
clB bssB bssStr2属于已初始化的全局变量和静态变量,在DATA段

int clA;
int clB = 10;

static int bssA;
static NSString *bssStr1;

static int bssB = 10;
static NSString *bssStr2 = @"bss";

- (void)testConst {   
NSLog(@"clA == \t%p",&clA);
    NSLog(@"bssA == \t%p",&bssA);
    NSLog(@"bssStr1 == \t%p",&bssStr1);
    
    NSLog(@"clB == \t%p",&clB);
    NSLog(@"bssB == \t%p",&bssB);
    NSLog(@"bssStr2 == \t%p",&bssStr2);
}

然后我们看打印结果
它们的内存分配有什么规律
在这里插入图片描述
可以看出:

未初始化的全局变量和静态变量,在BSS段
BSS段的地址分配时,是低地址 -> 高地址
已初始化的全局变量和静态变量,在DATA段
DATA段的地址分配,与变量定义的顺序无关

静态区安全测试

静态变量的作用范围是当前文件内。

相当于引用别的文件时,底层会深拷贝一份静态变量,放在了自己的文件中,以后访问及操作的都是本文件内的这个变量,对别的文件没有影响。

当前文件更改静态变量后,本文件内再访问,是更改后的值,但不影响别的文件中的这个静态变量的值。
别的文件引入静态变量后,拿到的是静态变量的初始值,修改后再访问是自己修改后的值。

static、extern、const关键字

extern
extern关键字用来声明变量或者函数是一个外部变量或者外部函数,也就是说告诉编译器该变量是在其他文件中定义的,编译的时候不要报错,在链接的时候按照字符串寻址可以找到这个变量或者函数

如果A.h中定义了全局变量比如int a;,那么在其他文件中的函数调用变量a的时候需要在对应头文件或者定义文件中(保证在使用这个变量前)使用extern int a;来声明这个变量,但是这样做有一个弊端,首先如果A.h中集中定义了大量的全局变量供其他文件使用,那么其他的调用文件中会重复的出现大量的extern语句,第二,如果其他文件直接引用A.h,那么会造成全局变量的重复定义,编译不过,等等

所以我们应该

1、在定义文件中定义全局变量, 比如A.m中定义全局变量 int aa;
2、在对应的头文件A.h中声明外部变量 extern int aa;
3、在使用aa变量的文件B.m中包含A.h;

在编译阶段,B编译单元虽然找不到该函数或变量,但它不会报错,它会在链接时从A编译单元生成的目标代码中找到此函数。

static
使用static修饰变量,就不能使用extern来修饰,即static和extern不可同时出现

static修饰的全局变量的声明与定义同时进行,即当你在头文件中使用static声明了全局变量,同时它也被定义了。

static修饰的全局变量的作用域只能是本身的编译单元。如果有多个文件包含了定义static变量的头文件,在其他编译单元使用它时,只是简单的把其值复制给了其他编译单元,其他编译单元会另外开个内存保存它,在其他编译单元对它的修改并不影响本身在定义时的值。(与头文件中定义const类似)

即在其他编译单元A使用它时,它所在的物理地址,和其他编译单元B使用它时,它所在的物理地址不一样,A和B对它所做的修改都不能传递给对方

多个地方引用静态全局变量所在的头文件,不会出现重定义错误,因为在每个编译单元都对它开辟了额外的空间进行存储。
一般定义static 全局变量时,都把它放在.m文件中而不是.h文件中,这样就不会给其他包含此头文件的编译单元里边重复生成变量。

在标准C++中引入命名空间之前,程序必须将名字声明为static,使他们用于当前编译单元。C++文件中静态声明的使用从C语言继承而来,在C语言中,声明为static的局部实体在声明它的文件之外不可见。
C++不赞成文件静态声明。C++标准取消了在文件中声明静态声明的做法。应该避免static静态声明,而在源文件中使用未命名的命名空间,在未命名的命名空间中定义变量。

未命名的命名空间仅在文件内部有效,其作用范围不会横跨多个不同的文件。

具体的例子

关于extern关键字

在头文件里这么写是合理的

//.h
static NSString * const myString = @"foo";

但是其实这是不正确也并不安全的一种写法

想让这个常量字符串被其他的类所正确使用
那么在我的头文件里应该这么声明

//.h
extern NSString * const myString;

然后在每个引用头文件的源文件内

//,m
NSString * const myString = @"foo";

何意呢家人们 为什么要这么写 到底安全在哪里?

在每个编译单元内,也就是通俗说在每个.m文件内
static const修饰的myString内容都是 foo 没问题
但这些myString其实是不同的对象

static修饰的全局变量的作用域只能是本身的编译单元。如果有多个文件包含了定义static变量的头文件,在其他编译单元使用它时,只是简单的把其值复制给了其他编译单元,其他编译单元会另外开个内存保存它,在其他编译单元对它的修改并不影响本身在定义时的值。

也就是说这里的static和const功能重复了
多个.m文件文件内存在的都是myString对象的复制
并没有实现多个文件共同使用咱们的一个myString对象

而extern关键字使我们的myString对象变成可共同使用的外部变量
然后就是注意extern的使用事项

1、在定义文件中定义全局变量, 比如A.m中定义全局变量 int aa;
2、在对应的头文件A.h中声明外部变量 extern int aa;
3、在使用aa变量的文件B.m中包含A.h;

关于static关键字

关于前面提到static的部分
我们来一段栗子

先在test1.h定义字符串 g_str

//test1.h
#ifndef TEST1H
#define TEST1H
static char g_str[] = "123456"; 
void fun1();
#endif

在test1.cpp中使用变量 g_str

//test1.cpp
#include "test1.h"
void fun1() {
cout << g_str << endl;
}

在test2.cpp中使用变量 g_str

//test2.cpp
#include "test1.h"
void fun2() {
cout << g_str << endl;
}

如果较真的同学偷偷调试上面代码会发现两个编译单元的g_str的内存地址相同
于是你下结论static修饰的变量也可以作用于其他模块
但那是你的编译器在欺骗你

大多数编译器都对代码都有优化功能,以达到生成的目标程序更节省内存,执行效率更高,当编译器在连接各个编译单元的时候,它会把相同内容的内存只拷贝一份

比如上面的"123456", 位于两个编译单元中的变量都是同样的内容,那么在连接的时候它在内存中就只会存在一份了

来个复杂的例子拆穿编译器的谎言:

在test1.cpp中使用变量 g_str

//test1.cpp
#include "test1.h"
void fun1() {
g_str[0] = 'a';
cout << g_str << endl;
}

在test2.cpp中使用变量 g_str

//test2.cpp
#include "test1.h"
void fun2() {
cout << g_str << endl;
}

void main() {
fun1(); // a23456
fun2(); // 123456
}

这个时候你在跟踪代码时就会发现两个编译单元中的g_str地址并不相同
因为你在一处修改了它,所以编译器被强行的恢复内存的原貌,在内存中存在了两份拷贝给两个模块中的变量使用

正是因为static有以上的特性,所以一般定义static全局变量时,都把它放在原文件中而不是头文件,这样就不会给其他模块造成不必要的信息污染

关于静态变量

全局静态变量

优点:不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会指向固定的指针地址,供所有对象使用,节省空间。
缺点:存在的生命周期长,从定义直到程序结束。
建议:从内存优化和程序编译的角度来说,尽量少用全局静态变量,因为存在的生命周期长,一直占用空间。程序运行时会单独加载一次全局静态变量,过多的全局静态变量会造成程序启动慢。

局部静态变量

优点:定义后只会存在一份值,每次调用都是使用的同一个对象内存地址的值,并没有重新创建,节省空间,只能在该局部代码块中使用。
缺点:存在的生命周期长,从定义直到程序结束,只能在该局部代码块中使用。
建议:局部和全局静态变量从本根意义上没有什么区别,只是作用域不同而已。如果值仅一个类中的对象和类方法使用并且值可变,可以定义全局静态变量,如果是多个类使用并可变,建议值定义在model作为成员变量使用。如果是不可变值,建议使用宏定义。

类的静态变量

在 iOS 中,类的静态变量具有以下特点和用途:

特点:

  1. 全局唯一性:静态变量在类的所有对象之间共享,只有一份存储空间。
  2. 生命周期:静态变量的生命周期从程序开始一直到程序结束。

用途:

  1. 共享数据:可以用于在类的不同对象之间共享一些通用的数据,例如全局的配置信息、计数器等。
  2. 实现单例模式:通过将构造函数私有化,并使用静态变量来存储唯一的实例,实现单例模式。

例如,以下是一个简单的示例,展示了在 iOS 中类的静态变量的使用:

@interface MyClass : NSObject

+ (void)incrementCounter;
+ (NSInteger)getCounter;

@end

@implementation MyClass

static NSInteger counter = 0;  // 定义静态变量

+ (void)incrementCounter {
    counter++;
}

+ (NSInteger)getCounter {
    return counter;
}

@end

在上述示例中,counter 就是 MyClass 类的静态变量,可以通过类方法进行操作和获取。

const

const修饰的全局常量据有跟static相同的特性(有条件的,const放在只读静态存储区),即它们只能作用于本编译模块中,但是const可以与extern连用来声明该常量可以作用于其他编译模块中
因为const对象默认在文件内有效,所以当多个文件中出现同名const时,其实等同于在不同文件中分别定义了独立的变量。

不同于变量,常量的值是固定不可变的,一般用于只读值。
优点:只可以读取值,不能修改。一般用于接口或者文字显示这种固定值。添加extern可以对外全局常量,任意位置都可以访问。
缺点:存在的生命周期长,从定义直到程序结束。需要在.h .m中分别定义代码较多。
建议:良好的编码习惯而言,少使用宏,多使用常量。因为常量声明是有明确类型的,而宏只是替换并不能进行类型判断。不够严谨。

《C++primer》中,讲到头文件中不可以包含定义,有三个例外: 类,常量表达式初始化的const对象,inline

在 C 和 C++ 编程中,通常建议头文件中不包含定义,主要是为了避免多重定义的问题。然而,确实存在三个例外情况:

  1. :在头文件中定义类是常见且被允许的。因为类的定义包含了成员函数的声明,而成员函数的实际定义可以在源文件中实现。

    例如:

    class MyClass {
        public:
            void myMethod();
    };
    
  2. 常量表达式初始化的 const 对象:如果一个 const 对象是用常量表达式进行初始化的,那么它可以在头文件中定义。因为具有常量初始化的 const 对象在多个编译单元中的定义是相同的,不会导致多重定义的问题。

    例如:

    const int MAX_SIZE = 100;
    
  3. inline 函数inline 函数在调用处展开,所以在多个源文件中包含其定义不会引起问题。

    例如:

    inline int add(int a, int b) {
        return a + b;
    }
    

这样的规则有助于保持头文件的简洁和避免潜在的链接错误。在实际编程中,遵循这些规则可以提高代码的可维护性和可移植性。

参考博客

一文看懂const extern static如何定义?究竟放在源文件还是头文件?
C++ extern/static/const区别与联系
019*:内存五大区:(栈、堆、全局静态区、常量区、代码区)(线程、函数栈、栈帧)
【iOS】—— 内存的五大分区
static const Vs extern const


原文地址:https://blog.csdn.net/m0_74703932/article/details/140433242

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!