【格蕾读C++ Primer Plus】第九章 内存模型和名称空间
1 單獨編譯
程序分為三份
不要#include源代碼文件,否則會多重聲明
1.1 頭文件
內容
不要把函數定義或者變量聲明放到頭文件中
頭文件中通常包含:
結構聲明,類聲明,模板聲明可以放在頭文件中,因為它們不創建變量,
而是指示編譯器生成和創建。
const數據和內聯函數有特殊的鏈接屬性,所以可以放在頭文件中。
可以將成員放在名稱空間中
引用方式
尖括號:編譯器在存儲標準頭文件的主機系統的文件系統中查找
雙引號:首先查找當前的工作目錄和源代碼目錄,如果沒找到,在標準位置找
同一個文件只能包含同一個頭文件一次,否則編譯錯誤
#ifndef COORDIN_H_ #define COORDIN_H_ //頭文件內容 #endif1.2 編譯流程
以UNIX執行C為例
1.3 名稱修飾
(71條消息) C+±-名字修飾_Emma-Zhang的博客-CSDN博客_c++名稱修飾
在C/C++中,一個程序要運行起來,需要經歷以下幾個階段:預處理、編譯、匯編、鏈接。
名字修飾(Name Mangling)是一種在編譯過程中,將函數、變量的名稱重新改編的機制,簡單來說就是編譯器為了區分各個函數,將函數通過一定算法,重新修飾為一個全局唯一的名稱。
兩個編譯器對同一個函數可能生成不同的修飾名稱,所以不同的名稱會使鏈接器無法將編譯器生成的函數調用與另一個編譯器生成的函數定義匹配。
因此在鏈接編譯模塊時,請確保所有對象文件或庫都是由同一個編譯器生成的。
2 存儲持續性,作用域,鏈接性
C++存儲方式是通過存儲持續性,作用域和鏈接性來描述的
2.1 存儲持續性
數據保存在內存中的時間
2.2 作用域
作用域(scope)描述名稱在文件的多大范圍可見。
局部
作用域為局部只在定義的代碼塊可用。
全局
作用域為全局(也叫文件作用域)的變量在定義位置到文件結尾都可用。
變量作用域
函數作用域
作用域可以是類或者名稱空間(包括全局),但不能是局部的,不然只對自己可見。
2.3 鏈接性
鏈接性(linkage)描述了名稱如何在不同單元之間共享。
鏈接性為外部的名稱可以在文件間共享
鏈接性為內部的名稱只能由一個文件中的函數共享
自動變量的名稱沒有鏈接性,因為它們不能共享。
3 變量的存儲方式
這里介紹引入名稱空間之前的情況
3.1 自動存儲持續性
函數中聲明的函數參數和變量:
存儲持續性:自動
作用域:局部,從聲明位置到代碼塊結束
鏈接性:無
分配內存的時機:執行到該代碼塊
同名自動變量
當兩個同名的變量一個位于外部,一個位于內部時,內部的將被解釋為局部代碼塊變量,為新定義。新定義隱藏了以前的定義,內部代碼塊中新定義可見,舊定義不可見。
auto關鍵字
C++11開始,auto用于自動類型推斷,但是在C中或者C++11之前,auto用于顯式地指出變量為自動存儲。
register關鍵字
寄存器變量:register關鍵字是由C語言引入的,建議編譯器使用CPU寄存器來存儲自動變量,旨在提高訪問變量的速度
在C++11以前,register表明變量用的很多,編譯器可以對其做特殊處理。
C++11后只限于指出變量是自動的
保留關鍵字的重要原因是避免使用了該關鍵字的現有代碼非法
3.2 靜態持續變量
靜態變量的數目在程序運行期間不變,所以不需要用棧管理,只需要固定內存分配,在程序執行期間一直存在。
如果沒有初始化,編譯器將靜態變量設為0,稱為零初始化的(zero-initialized)
靜態存儲持續性變量有三種鏈接性:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zuPN0hEO-1672942524360)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221230205803189.png)]
關鍵字重載
代碼塊內static指存儲持續性,代碼塊外部static表示內部鏈接性,這種關鍵詞含義取決于上下文的現象叫關鍵詞重載
靜態變量初始化
靜態變量都必須先零初始化,如果顯式初始化了,那就有兩種初始化方式
要調用函數,必須等到函數被鏈接且程序執行時,是動態初始化。
但是常量初始化可以使用sizeof()和C++11關鍵字constexpr,這增加了創建常量表達式的方式。
3.3 外部變量
外部鏈接性的靜態持續性的變量
鏈接性為外部的變量稱為**(常規)外部變量**,又稱為全局變量(相對于局部的自動變量),每個使用外部變量的文件必須聲明它。
單定義規則
C++有單定義規則:(one definition rule,ODR)變量只能有一次定義。
C++提供兩種變量聲明:
要在多個文件使用外部變量,需要在其他文件用extern聲明。
定義與全局變量同名的局部變量后,局部變量將隱藏全局變量。
作用域解析運算符
C++提供作用域解析運算符::,放在變量名前,表示使用變量的全局版本
3.4 靜態外部變量
指內部鏈接性的靜態持續性的變量
static限定符用于作用域為整個文件的變量,該變量的鏈接性變為內部,稱為靜態外部變量
如果在文件中定義了一個靜態外部變量,與另一個文件的常規外部變量重名,則該文件中隱藏常規外部變量
用處:在同一個文件中的多個函數間共享數據
3.5 無鏈接的靜態變量
指無鏈接性的靜態持續性的變量
函數內的靜態變量只在程序開始運行時被設置為0,當再次調用函數時不會再次初始化
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TcGnLnFB-1672942524361)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221231162728980.png)]
int a=1000; //外部鏈接性靜態變量 static int b=50; //內部鏈接性靜態變量 extern int c=20; int main() {...} void func1() {static int g =0;//無鏈接性靜態變量 }4 存儲說明符和cv-限定符
存儲說明符(storage class specifier)有
cv-限定符(cv-qualifier)(cv取兩個單詞的首字母)有
static關鍵字
作用域為整個文件時,表示內部鏈接性;局部聲明中,表示存儲持續性。
C++中static作用和使用方法
extern關鍵字
引用聲明,聲明引用在其他地方被定義的變量,見”外部變量“。
thread_local關鍵字
同一個聲明中不能使用多個說明符,除了thread_local,可以和static和extern結合使用
指出變量的持續性與其所屬線程的持續性相同,thread_local之于線程,猶如常規靜態變量之于整個程序。
const關鍵字
內存被初始化后,程序不能對它進行修改。
const char * const months[12]= {"January","February",··· };第一個const防止字符串被修改,第二個const確保數組每個指針始終指向它最初指向的字符串。
const全局變量
C++中,const全局變量的鏈接性為內部的,如同使用了static。這樣每個文件都有頭文件中定義的const變量但是不會因為單定義規則而沖突。
如果希望定義在函數外部的const變量的鏈接性為外部的,需要用extern關鍵字來覆蓋。而且必須在所有使用變量的文件中用extern聲明。
不管如何,只有一個文件能初始化。
//使用const //1.cpp extern const int states=50; //2.cpp extern const int states;//不使用const //1.cpp int states=50; //2.cpp extern int states;代碼塊中的const
作用域為代碼塊
volatile關鍵字
volatile:不穩定的
關鍵詞volatile表明:即使程序代碼沒有對內存單元進行修改,值也可能變化。
使用場景:
mutable關鍵字
mutable:會變的,可變的
關鍵詞mutable指出:即使結構/類為const,某個成員也可以被修改
struct data {char name[30];mutable int accesses; }; const data a ={"hi",0}; strcpy(a.name,"hello"); //不允許 a.accesses++; //允許5 其他鏈接性
5.1 函數的鏈接性
存儲持續性
C++和C都不允許在一個函數中定義另一個函數,因此所有函數的存儲持續性都自動為靜態的,在程序執行期間一直存在。
默認鏈接性
靜態鏈接性
如果定義了一個與庫函數同名的函數,編譯器將使用程序員定義的版本,而不是庫函數,但是C++保留了標準庫函數的名稱。
5.2 語言鏈接性
C語言中一個名稱只對應一個函數,C++同一個名稱可能對應多個函數,必須翻譯為不同的符號名稱,因此通過C++語言鏈接來實現。
C++語言鏈接:為函數名稱翻譯成不同的符號名稱,執行名稱矯正或者名稱修飾。
涉及鏈接程序對函數的處理。見書262
6 動態內存
動態內存由new和delete控制,而不是由作用域和鏈接性規則控制。可以在一個函數中分配,另一個函數中釋放。
存儲方案不適用于動態內存,但適用于用來跟蹤動態內存的自動和靜態指針變量。
float * p_fees =new float [20];
由new分配的動態內存會一直保留,但是聲明的語句塊執行完后,指針將消失,所以必須在代碼結束后返回地址,不然無法使用該內存。
6.1 分配內存
int *pi=new int (6); double *pd=new double (99.99);//列表初始化 struct where {double x;double y;double z;}; where *one =new where {2.5,5.3,7.2}//C++11 對結構 列表初始化int * ar =new int [4] {2,4,6,7};//C++11 對數組 列表初始化 double *pd =new double {99.99};//C++11 對單值變量 列表初始化分配函數 new()
void * operator new(std::size_t);
void * operator new[] (std::size_t);
使用運算符重載。size_t是個typedef,對應合適的整型
int * pi=new int;被轉化為int * pi=new(sizeof(int))
分配函數和釋放函數是可替換的,可以為new和delete提供替換函數
分配失敗
最初返回空指針,現在引發異常std::bad_alloc
6.2 內存釋放
程序結束后,new分配的內存通常將被釋放,但請求大型內存塊將導致代碼塊在程序結束不會被自動釋放
這時需要delete
釋放函數delete()
void operator delete(void *);
void operator delete[](void *);
6.3 定位new運算符
具體參考書P264
new運算符處理在堆中找內存塊還有一種變體,讓程序員可以指定要使用的位置,這樣可以處理通過特定地址進行訪問的硬件或者在特定位置創建對象。
與常規new的區別
常規new調用一個接受一個參數的new()函數,而定位new調用的接受兩個參數
頭函數:#include
使用定位new,變量后面可以有方括號也可以沒有
可以用于結構和對象等
用法
可以用靜態數組為定位new提供內存空間
char buffer[500]; int main(){int *p;p=new (buffer) int[20]; }工作原理
默認定位new函數:返回傳遞給定位new運算符的地址,并強制轉化為void *,這樣可以賦給任何指針類型
C++允許重載定位new函數,但定位new是不可替換的
7 名稱空間
為了防止兩個庫的名稱沖突,需要用名稱空間控制名稱的作用域
7.1 傳統的名稱空間
聲明區域|潛在作用域|作用域
聲明區域:可以在其中進行聲明的區域
潛在作用域:變量的潛在作用域從聲明點開始,到其聲明區域的結尾,潛在作用域比聲明區域小
作用域:變量對程序可見的范圍被稱為作用域
7.2 名稱空間
命名的名稱空間
特征
用作用域解析運算符來使用名稱空間來限定名稱,未被修飾的叫未限定的名稱,包含名稱空間的名稱叫限定的名稱
7.3 using聲明
using聲明由被限定的名稱和前面的關鍵字using組成
using聲明會把特定的名稱添加到所屬的聲明區域內
在函數中添加到局部聲明區域,覆蓋同名的全局變量;在函數外添加到全局名稱空間中
using聲明對于函數如果只給出了
namespace Jill {double fetch; } char fetch; int main() {using Jill::fetch;//double fetch; //錯的,已經定義了fetchcin>>fetch; //讀取Jill::fetchcin>>::fetch; //讀取全局的fetch }7.4 using 編譯指令
using聲明使得一個名稱可用,而using編譯指令使得所有的名稱都可用
using編譯指令由名稱空間名和using namespace組成
全局聲明區域內使用using namespace,使得名稱空間的全部名稱全局可用
函數中使用using編譯指令,則函數中可用
函數重載與using
C++using聲明和using指示_蓮娃的博客-CSDN博客_using
兩者比較
同:using編譯指令和using聲明存在可能的二義性,比如兩個名稱空間都定義了同樣的名稱,這時候編譯器不允許同時使用這兩個using聲明
異:using聲明與聲明類似,如果函數中已經聲明,則不能用其導入相同的名稱
using編譯指令類似于作用域解析運算符,讓函數內部將其視為函數之外聲明的。即如果已經有局部聲明,則名稱空間名被隱藏,但是仍然可以通過::變量名取得全局的對應變量,名稱空間::變量名取得名稱空間的對應變量
一般認為using聲明更加安全,因為編譯指令如果重名,編譯器不會警告,而且使用名稱空間的變量也沒有明確特征和說明
using namespace std; //減少這么使用int x; //用法1 std::cin>>x;using std::cin; //用法2 int x; cin>>x;名稱空間的嵌套
對于:
namespace elements {namespace fire{int flame;} }使用:using namespace elements::fire
再例如:
namespace myth {using Jill::fetch; //Jill名稱空間內的fetch變量using namespace elements; }直接使用:myth::fetch訪問即可
using編譯指令可傳遞,使用using namespace myth;相當于同時還使用了using namespace elements;
名稱空間別名
namespace m=myth; namespace MEF=myth::elements::fire; using MEF::flame;未命名的存儲空間
相等于鏈接性為內部的靜態變量
namespace {int counts } //等于 static int counts2;int main() {...}8 C++的內存分區
編譯器使用前三塊獨立內存,程序在此基礎上還需要代碼區:
數據區包括:堆,棧,靜態存儲區。
 靜態存儲區包括:常量區(靜態常量區),全局區(全局變量區)和靜態變量區(靜態區)。
 常量區包括:字符串常量區和常變量區。
 代碼區:存放程序編譯后的二進制代碼,不可尋址區。
9 小結
對于大型編程項目的管理:
當然,一個文件的程序不受此限制
10 留給讀者的問題
感謝看到這里,寫到凌晨兩點,麻煩點下贊謝謝
總結
以上是生活随笔為你收集整理的【格蕾读C++ Primer Plus】第九章 内存模型和名称空间的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 顺丰--Java丰桥接口整体封装(下单、
- 下一篇: springboot项目中引入Aspec
