C++内存模型

C++内存模型

通常编译器使用三块独立的内存:一块用于静态变量(可能还会被细分)、一块用于自动变量、一块用于动态存储。

存储持续性

C++存储持续性有以下类别:

  • 自动存储持续性:在函数定义中声明的变量(包括函数参数)。
  • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量。
  • 线程存储持续性(C++11):使用关键字thread_local定义的。生命周期与线程一样长。
  • 动态存储持续性:使用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。有时被称为自由存储/堆。

内存分配位置

  • 自动存储变量:分配到栈中,在程序执行其所属的函数/代码块时被创建,执行完函数/代码块时所使用的内存被释放。

  • 静态持续变量:分配到内存中固定位置,在程序整个运行过程中都存在。

    int global = 300; // static duration, external linkage
    static int one_file = 50; // static duration, internal linkage
    void funct1()
    {
    static int count = 0; // static duration, no linkage
    }

    静态持续变量首先会被初始化为0,若有显示初始化,然后再进行静态初始化(编译器根据文件按内容算出值进行初始化)或动态初始化(编译后在初始化,通常是调用了函数)。局部静态持续变量并非像自动存储变量那样,它只初始化一次。

  • 动态持续变量:使用new,会在堆中找到一个足以满足要求的内存块,不过也可使用定位new特性指定位置.

    #include <new>
    struct chaff
    {
    char dross[20];
    int slag;
    }
    char buffer[50];
    int main()
    {
    chaff *p = new (buffer) chaff;
    }

链接性

链接性为外部的变量称为外部变量,它们的存储持续性为静态。根据单定义规则,使用变量前需要声明,但只能定义一次。使用外部变量的文件中,必须先声明它,声明可分为两种:

  • 引用声明/声明:不给变量分配存储空间,因为它引用已有的变量。使用extern关键字,且不进行初始化,否则为定义声明,导致分配存储空间。
  • 定义声明/定义:他给变量分配存储空间。

如下示例,file01.cpp中全为定义,file02.cpp与file03.cpp全为声明。

// file01.cpp
extern int cats = 20;
int dogs = 22;
int fleas;
// file02.cpp
extern int cats;
extern int dogs;
// file03.cpp
extern int cats;
extern int dogs;
extern int fleas;

注意:const修饰的外部变量的链接性为内部,可再次使用extern关键字将其变为外部。

extern const int states = 50;

作用域

  • 自动存储变量:声明处到代码块结束。
  • 静态存储变量:开头为声明处,若为外部链接和內部链接则结尾是文件末尾,若为无链接则结尾是代码块结束。

注意作用域可能会被同名的”相对局部变量“隐藏。

// file01.cpp
int errors = 20;
// file02.cpp
static int errors = 25;
void f()
{
int errors = 30;
}

对于函数

函数的存储持续性都自动为静态,默认下链接性为外部,但可使用static关键字来修改为内部,使之只能作用于该文件。

static int private();
static int private()
{
...
}

内联函数不受单定义规则,意味着内联函数可放在头文件中被不同文件包含,各文件中均有定义,然而C++要求所有内联定义必须相同。

C++寻找函数的顺序:static函数在本身文件中寻找,否则先去所有程序文件中寻找,未找到去库文件中寻找。

修改语言链接性:

extern "C" void spiff(int);
extern void spoff(int);
extern "C++" void spaff(int);

名称空间

using声明和using编译是不一样的:使用了using声明时,就好像声明了相应的名称一样,如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用using编译指令时,将进行名称解析,就像在包含using声明和名称空间本身最小的声明区域中声明了名称一样。

#include <iostream>
namespace Jill
{
double fetch;
struct Hill {};
}
double fetch;
int main()
{
using namespace Jill;
// fetch = 3.0; // ERROR, global fetch and Jill::fetch
double fetch;
std::cin >> fetch; // local fetch
std::cin >> ::fetch; // global fetch
std::cin >> Jill::fetch; // Jill::fetch
}
int foom()
{
// Hill top; // ERROR
Jill::Hill crest; // valid
}

如上述代码,名称空间为全局的,main中using编译指令,根据”名称空间本身最小的声明区域中声明“,Jill中的变量会在文件这个声明区域中声明。所以main中声明的fetch会隐藏在全局声明的fetch和Jill全局声明的fetch,不过可以使用相应的作用域解析运算符来使用变量。需要注意的是,虽然函数中的using编译指令将名称空间的名称视为在函数之外声明的,但它不会使得该文件中的其他函数都能使用这些名称。如foom中无法直接使用Hill来表示全局的Jill::Hill。

在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它的引用了常量)。可使用匿名名称空间,此名称空间不能使用using声明和using编译指令,潜在作用域为从声明点到该声明区末尾。这相当于链接性为内部的静态变量附属品。

posted @   MeYokYang  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示