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

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

文章目录

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

全局区地址如何分配

先来一段代码

不难看出下面的clA bssA bssStr1属于未初始化的全局变量和静态变量,在BSS段

clB bssB bssStr2属于已初始化的全局变量和静态变量,在DATA段

objectivec 复制代码
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关键字

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

objectivec 复制代码
//.h
static NSString * const myString = @"foo";

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

想让这个常量字符串被其他的类所正确使用

那么在我的头文件里应该这么声明

objectivec 复制代码
//.h
extern NSString * const myString;

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

objectivec 复制代码
//,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

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

在test1.cpp中使用变量 g_str

objectivec 复制代码
//test1.cpp
#include "test1.h"
void fun1() {
	cout << g_str << endl;
}

在test2.cpp中使用变量 g_str

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

如果较真的同学偷偷调试上面代码会发现两个编译单元的g_str的内存地址相同

于是你下结论static修饰的变量也可以作用于其他模块

但那是你的编译器在欺骗你

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

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

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

在test1.cpp中使用变量 g_str

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

在test2.cpp中使用变量 g_str

objectivec 复制代码
//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 中类的静态变量的使用:

objectivec 复制代码
@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. :在头文件中定义类是常见且被允许的。因为类的定义包含了成员函数的声明,而成员函数的实际定义可以在源文件中实现。

    例如:

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

    例如:

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

    例如:

    cpp 复制代码
    inline int add(int a, int b) {
        return a + b;
    }

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

参考博客

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

相关推荐
DisonTangor12 小时前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone
- 羊羊不超越 -12 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
2401_865854881 天前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
HackerTom2 天前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈2 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_852403552 天前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
星际码仔2 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios
emperinter2 天前
WordCloudStudio:AI生成模版为您的文字云创意赋能 !
图像处理·人工智能·macos·ios·信息可视化·iphone
关键帧Keyframe2 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端
pb82 天前
引入最新fluwx2.5.4的时候报错
flutter·ios