2013-03-06 73 views
0

在C編碼的一個常見情況是編寫返回指針的函數。如果在運行時寫入函數內發生錯誤,則可能會返回NULL以指示錯誤。 NULL就是特殊的內存地址0x0,它不會用於任何事情,而是用來指示特殊情況的發生。在各種平臺上的C指針地址跨度

我的問題是,有沒有其他的特殊內存地址永遠不會用於用戶級應用程序數據?

我想知道這個的原因是因爲它可以有效地用於錯誤處理。試想一下:

#include <stdlib.h> 
#include <stdio.h> 

#define ERROR_NULL 0x0 
#define ERROR_ZERO 0x1 

int *example(int *a) { 
    if (*a < 0) 
     return ERROR_NULL; 
    if (*a == 0) 
     return (void *) ERROR_ZERO; 
    return a; 
} 

int main(int argc, char **argv) { 
    if (argc != 2) return -1; 
    int *result; 
    int a = atoi(argv[1]); 
    switch ((int) (result = example(&a))) { 
     case ERROR_NULL: 
      printf("Below zero!\n"); 
      break; 

     case ERROR_ZERO: 
      printf("Is zero!\n"); 
      break; 

     default: 
      printf("Is %d!\n", *result); 
      break; 
    } 
    return 0; 
} 

知道這絕不會通過用戶模式應用程序使用可以有效地用於更有效和更清潔的條件處理地址的一些特殊的跨度。如果你瞭解這一點,它適用於哪些平臺?我想跨度將是操作系統特定的。我主要對Linux感興趣,但是對於OS X,Windows,Android和其他系統也很瞭解。

回答

1

答案很大程度上取決於您的C編譯器以及您的CPU和操作系統,編譯後的C程序將在其中運行。

您的用戶空間應用程序通常永遠無法通過指向OS內核數據和代碼的指針訪問數據或代碼。而操作系統通常不會將這樣的指針返回給應用程序。

通常,它們也永遠不會獲得指向未由物理內存備份的位置的指針。你只能通過一個錯誤(一個代碼錯誤)或有目的地構造這樣一個指針來獲得這樣的指針。

C標準並沒有規定指針的有效範圍是和不是。在C中,有效指針是NULL指針或指向其生命週期尚未結束的對象的指針,這些指針可以是全局變量和局部變量,也可以是在malloc()'d內存和函數中創建的變量。

  • 指針代碼或數據對象未明確在它的源代碼級在C程序中定義(OS可以讓應用程序直接訪問它的一些代碼或數據,但這樣的:OS可以通過返回延伸該範圍是不常見的,或者操作系統可以讓應用程序訪問它們的一些部分,這些部分可以是應用程序加載時由操作系統創建的,也可以是應用程序編譯時由編譯器創建的部分,例如Windows讓應用程序檢查其可執行的PE映像,可以詢問Windows在內存中映像的起始位置)
  • 指向由操作系統爲應用程序分配的數據緩衝區的指針(這裏,通常,操作系統將使用自己的API而不是應用程序的malloc()/free()和您'd be r需要使用相應的操作系統特定功能來釋放此內存)
  • 特定於操作系統的指針不能被解除引用並僅用作錯誤指示符(例如,你可以有不只是一個undereferenceable指針更像NULL和你ERROR_ZERO是一個可能的候選人)

我通常不鼓勵使用硬編碼和魔術三分球程序。

如果由於某種原因,一個指針是溝通錯誤情況的唯一途徑,還有人一多,你可以這樣做:

char ErrorVars[5] = { 0 }; 
void* ErrorPointer1 = &ErrorVars[0]; 
void* ErrorPointer2 = &ErrorVars[1]; 
... 
void* ErrorPointer5 = &ErrorVars[4]; 

然後,您可以通過ErrorPointer1不同的錯誤返回ErrorPointer1條件,然後將返回的值與它們進行比較。雖然這裏有一個警告。您無法合法地使用>,>=,<,<=將返回的指針與任意指針進行比較。這隻在兩個指針指向或位於同一個對象時才合法。所以,如果你想快速檢查是這樣的:

if ((char*)(p = myFunction()) >= (char*)ErrorPointer1 && 
    (char*)p <= (char*)ErrorPointer5) 
{ 
    // handle the error 
} 
else 
{ 
    // success, do something else 
} 

它只會是合法的,如果p等於5個的那些錯誤指針之一。如果不是這樣,你的程序可以以任何想象和不可想象的方式行事(這是因爲C標準是這樣說的)。爲了避免這種情況,你必須指針針對每個錯誤指針分別比較:

if ((p = myFunction()) == ErrorPointer1) 
    HandleError1(); 
else if (p == ErrorPointer2) 
    HandleError2(); 
else if (p == ErrorPointer3) 
    HandleError3(); 
... 
else if (p == ErrorPointer5) 
    HandleError5(); 
else 
    DoSomethingElse(); 

又是什麼一個指針是什麼,它的表現是,是反編譯和OS/CPU特定。 C標準本身並沒有強制任何具體的表示或有效和無效指針的範圍,只要這些指針按C標準規定的那樣工作(例如指針運算與它們一起工作)即可。有一個good question on the topic。因此,如果您的目標是編寫可移植的C代碼,請不要使用硬編碼和「魔術」指針,而更喜歡使用其他方式來傳達錯誤條件。

+0

對於一個非常詳盡的解釋。 – Emanuel 2013-03-06 11:00:41

5

NULL只是特殊的存儲器地址0x0,它從來沒有用於任何事情,而是用於指示特殊情況的發生。

這是不完全正確的:有計算機NULL指針內部不是零(link)。

是否有任何其他特殊的內存地址永遠不會用於用戶級應用程序?

即使NULL不是通用的;沒有其他普遍未使用的內存地址,考慮到可編程的不同平臺的數量,這並不奇怪。

但是,沒有人會阻止你在內存中定義自己的特殊地址,並將其設置爲全局變量,並且將其視爲您的錯誤指標。這將適用於所有平臺,並且不需要特殊的地址位置。

在標題:

extern void* ERROR_ADDRESS; 

在C文件:

static int UNUSED; 
void *ERROR_ADDRESS = &UNUSED; 

此時,ERROR_ADDRESS指向一個全局唯一的位置(即,UNUSED的位置,這是本地的它被定義的編譯單元),您可以在測試指針的等式中使用它。

+0

哇!那是一個切肉刀!沒有想過自己註冊他們。它符合我所有的標準。驚人! – Emanuel 2013-03-06 10:51:20

1

它完全依賴於計算機和操作系統。例如,內存映射IO類的遊戲男孩前進的計算機上,你可能不想混淆地址「是什麼顏色的左上角像素」與用戶空間數據:

http://www.coranac.com/tonc/text/hardware.htm#sec-memory

+0

+1對於手持編程器的朋友:) – 2013-03-06 00:16:46

1

你不應該擔心程序員的地址,因爲它在不同的平臺上有所不同,實際的硬件地址和你的應用程序之間有很多層次。物理到虛擬的翻譯是其中之一,虛擬地址空間被映射到內存中,並且每個進程都擁有自己的地址空間,在大多數現代操作系統上,在其他進程的硬件級別上受到保護。

你在這裏指定的只是十六進制值,它們不被解釋爲地址。設置爲NULL的指針本質上是指它不指向任何東西,甚至不指向地址零。它只是NULL。不管它的價值是什麼,都取決於平臺,編譯器和其他許多東西。

未定義指向任何其他值的指針。指針是一個存儲另一個地址的變量,你要做的就是給這個指針一些其他有效值以外的值。

0

此代碼:

#define ERROR_NULL 0x0 
#define ERROR_ZERO 0x1 

int *example(int *a) { 
    if (*a < 0) 
     return ERROR_NULL; 
    if (*a == 0) 
     return (void *) ERROR_ZERO; 
    return a; 
} 

定義了一個函數example其取得輸入參數a和返回輸出的指針int。與此同時,當錯誤發生時,該功能會濫用轉換爲void*,以便以與返回正確的輸出數據相同的方式將錯誤代碼返回給調用方。這種方法是錯誤的,因爲調用者必須知道有時會收到有效的輸出,但它實際上並不包含所需的輸出,而是錯誤代碼

是否有任何其他的特殊內存地址永遠不會被使用......?
......它可以有效地用於錯誤處理

不要對可能被返回的可能的地址的任何假設。當你需要傳遞一個返回碼給調用者時,你應該以更直接的方式來做。你可以採取指針的輸出數據作爲參數,並返回識別成功或失敗的錯誤代碼:

#define SUCCESS  0x0 
#define ERROR_NULL 0x1 
#define ERROR_ZERO 0x2 

int example(int *a, int** out) { 
    if (...) 
     return ERROR_NULL; 
    if (...) 
     return ERROR_ZERO; 
    *out = a; 
    return SUCCESS; 
} 
... 
int* out = NULL; 
int retVal = example(..., &out); 
if (retVal != SUCCESS) 
    ... 
0

其實NULL(0)是一個有效的地址。但這不是您通常可以寫信的地址。

從內存中,NULL可能是一些舊的C編譯器舊VAX硬件上的不同值。也許有人可以證實這一點。它現在總是爲0,因爲C標準定義了它 - 看到這個問題Is NULL always false?

通常,從函數返回錯誤的方式是設置errno。如果錯誤代碼在特定情況下是有意義的,那麼您可以捎帶回去。但是,如果您需要自己的錯誤,那麼您可以做與errno方法相同的事情。

個人而言,我寧願不返回void *,但使函數採用void **並返回結果。然後,您可以直接返回錯誤代碼,其中0 =成功。

例如

int posix_memalign(void **memptr, size_t alignment, size_t size); 

注意分配的內存在memptr中返回。結果代碼由函數調用返回。不像malloc。

void *malloc(size_t size) 
0

在Linux上,在64位上以及使用x86_64架構(來自Intel或AMD)時,只使用64位總地址空間的48位(硬件限制AFAIK)。基本上,2^47之後的任何地址直到2^62可以使用現在,因爲它不會被分配。

在某些背景下,Linux進程的虛擬地址空間由用戶和內核空間組成。在上述提及架構中,前47位(128 TB)用於用戶空間。內核空間用於光譜的末端,因此在完整的64位地址空間的末尾有最後128個TB。在之間是terra incognita。雖然這可能會在未來的任何時候改變,這是不便攜的。

但我可以想出很多其他的方法來返回一個錯誤比你的方法,所以我沒有看到使用這種黑客的優勢。