C++知识整理 内存模型和命名空间
在读《C++ primer》的过程中整理一下知识点,做点笔记。
单独编译:
C++鼓励将组件函数放到独立的文件中,然后可以单独编译这些文件,将他们链接成可执行程序。在大型项目中,如果只改变了一个文件,则可以对这个文件进行单独编译,然后将它与其他文件的编译版本链接即可。
.h文件和.cpp文件中应该存放的内容:
1. 头文件 XXX.h文件:
a. 包含结构声明和使用这些结构的函数原型
b. 类声明
c. 模板声明
d. 内联函数
e. 使用#define 或者 const 定义的符号常量
2. 源代码文件, .cpp文件
a. 存放函数的具体实现
在程序中包含头文件时,需要注意:
1. 是自己定义的头文件,应该使用#include "xxx.h"的形式
2. 如果时c++中标准头文件,应该使用#include <xxx.h>的形式
对于C++编译器而言,如果头文件包含在尖括号中,则它将在存储标准头文件的主机系统的文件系统中查找。如果文件名包含在双引号中,则编译器会首先查找当前的工作目录或者源代码目录,如果没有找到,才会在存储标准头文件的文件系统中查找。
多个库的链接:
由不同编译器创建的二进制模块很可能无法正确的链接,这是应为c++标准允许每个编译器设计人员以他认为合适的方式实现名称修饰。名称的不同将使连接器无法将一个编译器生成的函数调用和另一个编译器生成的函数定义匹配。所以在链接时,要保证所有的对象文件或者库都是由同一个编译器生成的。如果游玩代码,完全可以自己重新编译。
关于名称修饰,可以参考一下这篇文章:《C++中的名称修饰》
名称修饰在C++中存在着广泛的使用。第一个C++编译器将C++代码转换为C代码,然后使用C编译器来编译从而得到目标代码;正因为如此,符号的名称需要符合C语言的标志符规则。即使后来,C++的编译器能够直接将将C++代码转换为机器码或者汇编代码,系统的连接器却通常不支持C++的符号,所以名称修饰仍然需要。
C++编程语言并未规定标准的名称修饰方案,所以每一种编译器按照自己的方法实现。C++由于具有一些复杂的语言特性,比如:类,模板,命名空间,运算符重载等,这会改变了特定符号在上下文或者使用中的含义。这些特性的元信息可以通过修饰名称的符号来消除。因为这种名称修饰系统在不同的编译器之间并没有标准化,几乎没有链接器能够链接不同的编译器产生的目标文件。
下面举一个简单的例子:
有下面的c++程序:
int f (void) { return 1; }
int f (int) { return 0; }
void g (void)
{int i = f();int j = f(0);
}
这些是不同的函数,相互之间除了名称没有其他关联。如果这些函数不做任何改变而直接转换为C代码,会带来一个错误——C语言不允许存在两个同名的函数。所以C++编译器需要将函数的签名信息编码到函数的符号名称中,结果大概如下所示:
int __f_v (void) { return 1; }
int __f_i (int) { return 0; }
void __g_v (void)
{ int i = __f_v();int j = __f_i(0);
}
注意函数g() 的名称也被修饰了,即使不存在与函数g() 的名称相冲突的地方:名称修饰会应用到所用的符号上。
(来源:《C++中的名称修饰》)
存储持续性,作用域和链接性:
根据数据在内存中的保留时间的不同,c++中数据存储有三种不同的方案:
1. 自动存储持续性:
在函数定义中声明的变量的存储持续性为自动。在函数执行时被创建,函数执行完毕后销毁。c++有两种存储持续性为自动的变量。即,如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。
1.1 自动变量和栈:
对于典型的c++编译器,对于自动变量的管理按照如下的机制:自动变量的数目随函数的开始和结束而变化,程序对自动变量的管理方法是,留出一段内存,将其视为栈,程序使用两个指针来跟踪栈,指向栈底的指针和指向栈顶的指针。在函数调用过程中,函数中的变量被压入栈中。函数执行结束的时候,栈顶的指针重新指向开始的位置。
寄存器变量:
register关键字最初由C语言引进,他建议编译器使用cpu的寄存器来存储自动变量: register int count; 这是为了提高访问变量的速度。c++11之后,这个关键字的作用只是显示的指出变量是自动的。现在保留该关键字的作用只是为保证以前用了register关键字的代码是合法的。
2. 静态存储持续性:
在函数定义的外面定义的变量和使用关键字static定义的变量的存储持续性都为静态,他们在程序的整个运行过程中都存在。c++为存储持续性为静态的变量提供了三种链接:
a. 外部链接: 可在其他文件中访问
b. 内部链接: 只能在当前文件中访问
c. 无链接:只能在当前函数或者代码块中访问。
这三种连接性在程序执行的整个周期内都存在。对比自动变量,在程序运行的过程中,静态变量的数目是保持不变的,所以不需要特殊的装置(栈)来存储他们。编译器会为它们分配固定的内存来存储静态变量。并且如果没有显式的初始化,编译器将把他们设置为0.(这称为静态初始化)
例如:
int global = 100; // 静态变量,外部链接性 外部文件可以调用
static int onefile = 200; // 静态变量,内部链接性
int main()
{} void func()
{static int call = 0; // 静态变量 无链接性 }
静态变量的初始化方法:
a. 静态初始化:变量在编译器编译,处理文件的时候被初始化
// 静态初始化
int x;
int y = 6;
int z = 34*23; // 编译器会进行简单的计算
b. 动态初始化:变量在编译器编译,处理文件后被初始化:
// 动态初始化,变量的初始化必须等到函数被连接且程序执行的时候
const double pi = 4.0 * atan(1.0);
3.线程存储持续性:
如果变量的声明是使用关键字thread_local声明的,则其生命周期与所述的线程一样长。
4. 动态存储持续性:
用new运算符分配的内存将一直存在,直到使用delete将其释放或者程序结束为止。有时候称为自由存储(free storage)或者堆。
--------------------------------------------------------------------------------------------------------
外部链接性:
C++中的但定义规则(ODR: one defination rule), 变量只能有一次定义。对于具有外部链接性的变量,c++提供了两种声明方式:
1. 定义声明:
即定义,为变量分配实际的内存空间
2. 引用声明:
即在外部文件引用变量,使用extern关键字。指标是引用,且不能进行赋值。如果进行赋值,则表示的是定义变量。
举个例子:
// file1.cpp
extern int cat = 10; // 定义,因为初始化了
int dog = 20;// file2.cpp
extern int cat; // 引用
extern int dog; // 引用 // file3.cpp
extern int cat; // 引用
extern int dog; // 引用 void func()
{int cat = 30; // 局部变量cout << cat << endl; // 输出30 // 定义同名的局部变量以后,会将全局隐藏cout << ::cat << endl; // 通过这种方式调用被隐藏的全局变量 }
内部链接性:
可以使用链接性为内部的静态变量在同一文件中的的多个函数之间共享数据。如果将做作用域为整个个文件的变量变为静态的,就不必担心其名称与其他文件中的作用域为整个文件的同名变量发生冲突。
无链接性:
子函数或者代码块的内部,使用static修饰的变量。这意味着即使该函数或者代码块没有被调用,其中的静态变量依然存在于内存中。因此在两次函数调用之间,静态局部变量的值保持不变。在函数中,静态变量知会初始化一次,即使再次调用函数时,也不会初始化。
函数的链接性:
c++中,所有函数的存储持续性都是静态的。默认情况下,函数的链接性为外部。即可以在不同文件之间进行共享。另外,可以使用static关键字将函数的链接型设置为内部的。必须在函数原型和函数定义中同时使用static关键字。
static int func(int a); // prototype
static int func(int a)
{ ...
}
-----------------------------------------------------------------------------------------------------------------------------
cv-限定符:
const: 内存初始化后,程序便不对它进行修改
volatile:即使代码没有对内存单元进行修改,它也可能发生变化。例如多线程中遇到的问题。
举个例子:
假设编译器发现,程序在几条语句中两次使用了某一变量,编译为了优化,可能不会两次让程序去查找这个值,而实将这个值缓存到寄存器中,但是,这种优化的前提条件是,这个变量的值在前后两次的使用中,其值不会发生变化。所以,将变量声明为volatile,相当于告诉编译器不要进行这种优化。
mutable限定符:
即使结构(类)变量为const,其某个成员也可以被修改,例如:
// 定义结构
struct data
{char name[30];mutable int access;
};const data cdata = {"hello", 23}; // const类型的结构变量
const strcpy(cdata.name, "world"); // 不允许
cdata.access = 89; // 允许修改
语言链接性:
连接程序要求每个函数有不同的符号名,在C语言中,一个名称只能对应一个函数,因此这很容易实现。例如,C比那一起可能将函数名func()翻译为func_()这样的形式。这种方法称为C语言的链接性。在C++中一个名称可能对应多个函数,编译器必须将这些函数翻译成不同的符号名。例如,func(int x)可能会翻译为func_i, func(double x, double)可能会翻译为func_d_d。这种方法就是c++语言的链接性。这个就是前面所提到的名称修饰的过程。
名称空间(namespace)
在程序中,可能会使用不同厂商提供的库,不同的库中可能定义了同名的函数,这样可能会导致冲突。所以C++提供了名称空间工具,可以更好的控制名称的作用域。
变量的声明区域:指可以在其中声明的区域,例如在函数外面声明全局变量,则声明区域位为其所在文件,在函数内声明变量,则其声明区域为所在的代码块。
变量的潜在作用域:从声明点开始,到其声明区域的结束。因此潜在作用域比声明区域小。
名称空间特性:
相当于提供一个声明名称的区域,不同的名称空间中,即使相同的名称也不会发生冲突,这就很好的解决了不同厂商提供的库文件如果有同名的函数,该如何调用的问题。例如:
namespace Space1
{int pail;void fetch();double pi;
}namespace Space2
{double fetch;double pail;
}
名称空间可以是全局的。也可以位于另一个名称空间中,但不能在代码块中。所以它的链接性是外部的。调用方法如下:
Space2::fetch; // 作用域解析运算符
Space1::fetch();
using声明和 using编译指令:
为了避免每次都需要使用作用域解析运算符对名称空间中的名称进行调用,c++提供了两种机制:
1. using声明:使一个名称可用
using Space1::pail; // using声明
2. using编译指令:是整个名称空间可用
using namespace Space2; // using编译指令
例如:
namespace Space1
{int pail;void fetch();double pi;
}namespace Space2
{double fetch;double pail;
}Space2::fetch;
Space1::fetch();
int pail;int main()
{using Space1::pail; // using声明using namespace Space2; // using编译指令using Space1::pail; // 名称空间中的变量::pail = 9; // 全局变量 return 0;
}
一般推荐使用using声明:只导入指定的名称,如果导入的名称与局部的名称冲突时编译器会进行提示。如果使用using编译指令,则不会提示,如果有冲突,局部名称会覆盖掉导入的名称空间。
名称空间也可以实现嵌套。
也可以给名称空间创建别名:
namespace sp1 = Space1;
通过一个完整的程序说明名称空间
h文件
#pragma once
#include "stdafx.h"#include <string>namespace pers // 定义名称空间
{struct Person{std::string lname;std::string fname;};void getPerson(Person& );void showPerson(const Person&);
}namespace debts
{using namespace pers;struct debt{Person name;double amount;};void getDebt(debt&);void showDebt(const debt&);double sumDebts(const debt arr[], int n);
}
.cpp文件:
#include "namesp.h"
#include <iostream>
#include "stdafx.h"
// 定义和声明必须在同一个名称空间中
namespace pers
{using std::cout;using std::endl;using std::cin;void getPerson(Person& p){cout << "Enter first name: ";cin >> p.fname;cout << "Enter last name: ";cin >> p.lname;}void showPerson(const Person& p){cout << "This is " << p.fname << "." << p.lname << endl;}
}namespace debts
{void getDebt(debt& d){getPerson(d.name);cout << "Enter the debt: " << endl;cin >> d.amount;}void showDebt(const debt& d){showPerson(d.name);cout << "The debt is " << d.amount << endl;}double sumDebts(const debt arr[], int n){double sum_ = 0.0;for (int i = 0; i < n; i++){sum_ += arr[i].amount;}return sum_;}
}
C++知识整理 内存模型和命名空间相关推荐
- JAVA学习笔记--4.多线程编程 part1.背景知识和内存模型
2019独角兽企业重金招聘Python工程师标准>>> 背景知识 CPU Cache 如上简易图所示,在当前主流的CPU中,每个CPU有多个核组成的.每个核拥有自己的寄存器,L1,L ...
- C++学习笔记(三)——面向过程编程的C++之内存模型和命名空间
一.存储持续性回顾 在第一篇中我们讨论了C++的内存方案,即存储类别如何影响信息在文件间的共享. C++使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间 ...
- 《C++ Primer Plus(第六版)》(13)(第九章 内存模型和命名空间 笔记)
1.为了保持通用性,C++使用术语翻译单元,而不是文件,文件不是计算机组织信息时的唯一方式. 2.C++允许编译器使用自己的名称修饰,因此不同的编译器编译出来的二进制模块(对象代码文件),通常是无法链 ...
- 《JAVA并发编程的艺术》之Java内存模型
<JAVA并发编程的艺术>之Java内存模型 文章目录 <JAVA并发编程的艺术>之Java内存模型 Java内存模型的基础 并发编程模型的两个关键问题 Java内存模型的抽象 ...
- JVM内存模型JVM内存模型
转自 https://www.cnblogs.com/xing901022/p/7725961.html Java开发有个很基础的问题,虽然我们平时接触的不多,但是了解它却成为Java开发的必备基础- ...
- Java并发知识梳理(上):并发优缺点,线程状态转换,Java内存模型,Synchronized,Volatile,final,并发三特性,Lock与AQS,ReetrandLock
努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! 整个系列文章为Java并发专题,一是自己的兴趣,二是,这部分在实际理解上很有难度,另外在面试过程中也是经常被问到.所以在学习过程中,记 ...
- java虚拟机内存模型与垃圾回收知识复习总结
今天日子很特殊,1024程序员节,本来每个月计划的是至少一篇博客,刚好这个月还没写,今天的日志又特殊,必须要写一篇博客. 之前看过一些讲java虚拟机的课程,但是学过容易忘,总结一下,平时可以多看看. ...
- 操作系统知识整理——Linux下进程的内存布局以及brk()、sbrk()函数探究
文章目录 前言 一.内存堆栈模型 二.系统栈和用户栈 三.函数调用时的内存栈分配 四.brk(), sbrk() 用法详解 前言 本篇文章是自己在学习xv6操作系统内核时,发现自己对进程在内存中的布局 ...
- 《深入理解java内存模型》学习整理1
为什么80%的码农都做不了架构师?>>> 1:在java中,所有实例域.静态域和数组元素存储在堆内存中,堆内存在线程之间共享. 2:局部变量.方法定义参数和异常处理器参数不会在 ...
最新文章
- 【一语点醒梦中人】如何优雅地合并两个JSON对象 → Object.assign(a, b)和Object.assign({}, a, b)的区别
- 两道JVM面试题,竟让我回忆起了中学时代!
- 从零开始学PowerShell(1)初见基础命令
- 护航亚运|安恒信息推出“九维五星” ,并强调将全面突出“智能亚运”
- SCI投稿中的简写(ADM,AE,EIC等)与状态解读
- 每天学一点Linux(一)——apt-get
- 关于ini读取错误问题?
- 安全基础--21--安全运维
- js传参不是数字_js中函数传参方式
- 老泪纵横!伴随数代人成长的中国经典动画
- 学计算机平面设计的基础知识,新手学习平面设计2要点_计算机平面设计
- matlab2014simulink中的三相晶闸管整流桥怎么找_哈尔滨有源滤波组件HPD2000-100-4L坏了怎么办 - 哈尔滨照明工业...
- 外研社计算机英语试题,外研社七年级下册英语期末试卷
- 计算机应用基础(专)【7】
- ORACLE process爆满,大量process没有对应的session
- 【杂记】全栈开发中碰到的一些问题及解决方法
- matlab 函数不定参数,matlab function定义一个函数,但一直出来说输入参数数目不足。我用的是2014版本,不知道数目原因啊?...
- Springboot物资发放管理系统
- Pandas中replace替换问题
- OAuth认证(完整版)
热门文章
- k8s核心技术-Pod(调度策略)_影响Pod调度(污点和污点容忍)---K8S_Google工作笔记0027
- Netty工作笔记0002---Netty的应用场景
- STM32工作笔记0020---新建工程模板基于寄存器-M3
- SVM 训练--在训练集上acc为94% 在测试集上为70%
- Linux操作问题解答
- php调用mysql中文变量_用php调用MySQL里的数据,为什么汉字都变成了问号?在线等...
- 计算机综合应用上机考试题库,2016年计算机上机考试题库
- html5 在新标签页打开,Chrome,Javascript,window。在新标签页中打开
- java前台计算date差_js前台计算两个日期的间隔时间
- python中的参数_python中参数解析