1. 程式人生 > 實用技巧 >儲存持續性、作用域和連結性之二

儲存持續性、作用域和連結性之二

除了執行緒儲存持續性,C++使用三種不同的方案來儲存資料,這些方案的區別就在於資料保留在記憶體中的時間。

  • 自動儲存持續性:在函式定義中宣告的變數(包括函式引數)的儲存持續性是自動的。它們在程式開始執行其所屬的函式或程式碼塊時被建立,在執行完函式或程式碼塊時,它們使用的記憶體被釋放。C++有兩種儲存持續性為自動的變數。
  • 靜態儲存持續性:在函式定義外定義的變數和使用關鍵字static定義的變數的儲存持續性為靜態。它們在程式整個執行過程中都存在。C++有三種儲存持續性為靜態的變數。
  • 動態儲存持續性:用new運算子分配的記憶體將一直存在,直到使用delete運算子將其釋放或程式結束為止。這種記憶體的持續性為動態,有時也被成為自由儲存(free store)和堆(heap)。

動態儲存持續性

使用C++運算子new(或C函式malloc())分配的記憶體,被成為動態記憶體。動態記憶體由運算子newdelete控制,不是由作用域和連結性規則控制。因此,可以在一個函式中動態分配記憶體,而在另一個函式中將其釋放。其分配和釋放的順序取決於newdelete在何時以何種方式被使用。

雖然儲存方案概念不適用於動態記憶體,但適用於用來跟蹤動態記憶體的自動和靜態指標變數。例如,假設在一個函式中包含下面的語句:

float * p_fees = new float [20];

new分配的80個位元組(假設float 為4個位元組)的記憶體將一直保留在記憶體中,直到使用delete

運算子將其釋放。但當包含該宣告的語句塊執行完畢時,p_fees 指標將消失。如果希望另一個函式能夠使用這80個位元組中的內容,則必須將其地址傳遞或返回給該函式。另一方面,如果將p_fees 的連結性宣告為外部的,則檔案中位於該聲明後面的所有函式都可以使用它。另外,通過在另一個檔案中使用下述宣告,便可在其中使用該指標:

extern float * p_fees;

使用new運算子初始化

如果要為內建的標量型別(如 intdouble)分配儲存空間並初始化,可在型別名後面加上初始值,並將其用括號括起:

int *pi = new int (6); // *pi set to 6
double * pd = new double (99.99); // *pd set to 99.99

然而,要初始化常規結構或陣列,需要使用大括號的列表初始化,這要求編譯器支援C++11。C++11允許您這樣做:

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

在C++11中,還可將列表初始化用於單值變數:

int *pi = new int {6}; // *pi set to 6
double * pd = new double {99.99}; // *pd set to 99.99

new失敗時

new可能找不到請求的記憶體量。在最初的10年中,C++在這種情況下讓 new返回空指標,但現在將引發異常std:.bad_alloc。

定位new運算子

通常,new負責在堆(heap)中找到一個足以能夠滿足要求的記憶體塊。new運算子還有另一種變體,被稱為定位(placement) new運算子,它讓您能夠指定要使用的位置。程式設計師可能使用這種特性來設定其記憶體管理規程、處理需要通過特定地址進行訪問的硬體或在特定位置建立物件。即定位new運算子可以在指定地址建立物件、將指定記憶體分配給指定物件。

#include <new>

struct chaff
{
    char dross[20];
    int slag;
};
char buffer1[50]
char buffer2[500];

int main()
{
    chaff *p1, *p2;
    int *p3, *p4;
    
    // 首先, new的常規形式
    p1 = new chaff;     // 放置結構體到堆中
    p3 = new int[20];   // 放置整型陣列到堆中
    
    // 然後, 兩種形式的定位new, placement new
    p2 = new (buffer1) chaff;   // 放置結構體放到 buffer1 中
    p4 = new (buffer2) int[20]; // 放置整型陣列到 buffer2 中
    ...
}

注意,buffer1buffer2都位於delete的管轄區域之外,不能使用delete釋放記憶體塊。另一方面,如果buffer是使用常規new運算子建立的,便可以使用常規delete運算子來釋放整個記憶體塊。

定位new運算子的另一種用法是,將其與初始化結合使用,從而將資訊放在特定的硬體地址處。

定位new的其他形式

int * pi = new int;                 // invokes new(sizeof (int))
int * p2 = new(buffer) int;	        // invokes new(sizeof(int),buffer)
int * p3 = new(buffer) int [40];    // invokes new(40*sizeof(int), buffer)