簡體   English   中英

將 int 轉換為指針 - 為什么先轉換為 long? (如 p = (void*) 42; )

[英]Cast int to pointer - why cast to long first? (as in p = (void*) 42; )

GLib文檔中,有一章是關於類型轉換宏的。 在將int轉換為void*指針的討論中,它說(強調我的):

天真地,您可能會嘗試這樣做,但這是不正確的:

 gpointer p; int i; p = (void*) 42; i = (int) p;

同樣,那個例子是不正確的,不要復制它。 問題是在某些系統上你需要這樣做:

 gpointer p; int i; p = (void*) (long) 42; i = (int) (long) p;

(來源:GLib 2.39.92 的 GLib 參考手冊,章節類型轉換宏)。

為什么需要轉換為long

任何需要的int擴展是否應該不會作為指針轉換的一部分自動發生?

根據C99: 6.3.2.3引用:

5 整數可以轉換為任何指針類型。 除了前面指定的之外,結果是實現定義的,可能沒有正確對齊,可能沒有指向引用類型的實體,並且可能是陷阱表示。56)

6 任何指針類型都可以轉換為整數類型。 除了前面指定的,結果是實現定義的。 如果結果不能以整數類型表示,則行為未定義。 結果不必在任何整數類型的值范圍內。

根據您提到的鏈接中的文檔:

指針的大小始終至少為 32 位(在 GLib 打算支持的所有平台上)。 因此,您可以在指針值中存儲至少 32 位整數值。

並且保證long時間至少為 32-bits

所以,代碼

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;

正如 GLib 所宣傳的那樣,它更安全、更便攜,並且僅適用於最多 32 位整數。

glib 文檔是錯誤的,無論是對於他們(自由選擇的)示例還是一般而言。

gpointer p;
int i;
p = (void*) 42;
i = (int) p;

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;

在所有符合 c 的實現上都會導致ip相同值。
該示例選擇不當,因為42保證可以用intlong表示(C11 草案標准 n157: 5.2.4.2.1 Sizes of integer types )。

一個更具說明性(和可測試)的例子是

int f(int x)
{
  void *p = (void*) x;
  int r = (int)p;
  return r;
}

這將往返int值如果void*可以表示int可以表示的每個值,這實際上意味着sizeof(int) <= sizeof(void*) (理論上:填充位,yadda,yadda,實際上並不重要) . 對於其他整數類型,同樣的問題,同樣的實際規則( sizeof(integer_type) <= sizeof(void*) )。

相反,真正的問題,正確說明:

void *p(void *x)
{
  char c = (char)x;
  void *r = (void*)c;
  return r;
}

哇,這是不能工作的可能,對吧? (實際上,它可能)。 為了往返指針(軟件已經做了很長時間沒有必要),您必須確保您往返的整數類型可以明確表示指針類型的每個可能值。

從歷史上看,很多軟件是由猴子編寫的,它們假設指針可以通過int來回傳輸,這可能是因為 K&R c 的隱式int -"feature" 以及很多人忘記#include <stdlib.h>然后轉換malloc()的結果malloc()到指針類型,從而意外地通過int往返。 在機器上,代碼是為sizeof(int) == sizeof(void*) ,所以這是有效的。 當切換到具有 64 位地址(指針)的 64 位機器時,很多軟件都期望兩個互斥的東西:

1) int是一個 32 位 2 的補碼整數(通常也期望有符號溢出環繞)
2) sizeof(int) == sizeof(void*)

一些系統(Windows)也假定sizeof(long) == sizeof(int) ,大多數其他系統有 64 位long

因此,在大多數系統上,將往返中間整數類型更改為long固定(不必要地損壞)代碼:

void *p(void *x)
{
  long l = (long)x;
  void *r = (void*)l;
  return r;
}

當然,在 Windows 上除外 從好的方面來說,對於大多數非 Windows(和非 16 位)系統sizeof(long) == sizeof(void*)是正確的,因此往返是雙向的

所以:

  • 這個例子是錯誤的
  • 選擇保證往返的類型不保證往返

當然,c 標准在intptr_t / uintptr_t (C11 草案標准 n1570: 7.20.1.4 Integer types能夠保持對象指針)中一個(自然符合標准的)解決方案,它被指定為保證
指針 -> 整數類型 -> 指針
往返(雖然不是相反)。

我認為這是因為這種轉換是依賴於實現的。 為此最好使用uintptr_t ,因為它在特定實現中是指針類型的大小。

正如在Askmish回答中所解釋的,從整數類型到指針的轉換是由實現定義的(參見例如N1570 6.3.2.3 指針§5 §6和腳注67 )。

從指針到整數的轉換也是實現定義的,如果結果不能用整數類型表示,則行為是undefined

現在,在大多數通用架構上, sizeof(int)小於sizeof(void *) ,因此即使是那些行

int n = 42;
void *p = (void *)n;

當用 clang 或 gcc 編譯時會產生警告(參見例如這里

warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]

從 C99 開始,頭文件<stdint.h>引入了一些可選的固定大小類型。 一對夫婦,特別是,應該在這里使用n1570 7.20.1.4 能夠保存對象指針的整數類型

以下類型指定了一個有符號整數類型,其屬性是任何指向 void 的有效指針都可以轉換為該類型,然后轉換回指向 void 的指針,並且結果將與原始指針相等:

 intptr_t

以下類型指定了一個無符號整數類型,其屬性是任何指向 void 的有效指針都可以轉換為該類型,然后轉換回指向 void 的指針,並且結果將與原始指針相等:

 uintptr_t

這些類型是可選的。

因此,雖然long可能比int更好,但為了避免未定義的行為,最可移植(但仍由實現定義)的方法是使用其中一種類型(1)

Gcc 的文檔指定了轉換是如何發生的

4.7 數組和指針

將指針轉換為整數或反之的結果 (C90 6.3.4、C99 和 C11 6.3.2.3)

如果指針表示大於整數類型,則從指針到整數的轉換會丟棄最高有效位,如果指針表示小於整數類型,則符號擴展(2) ,否則位不變。

如果指針表示小於整數類型,則從整數到指針的轉換丟棄最高有效位,如果指針表示大於整數類型,則根據整數類型的符號進行擴展,否則位不變。

當從指針轉換為整數並再次返回時,結果指針必須引用與原始指針相同的對象,否則行為未定義。 也就是說,不能使用整數算術來避免 C99 和 C11 6.5.6/8 中禁止的指針算術的未定義行為。
[...]
(2) GCC 的未來版本可能會零擴展,或使用目標定義的 ptr_extend 模式。 不要依賴符號擴展。

其他的, 嗯...


n1570 6.3.1.3 Signed and unsigned integers中提到了不同整數類型(本例中為intintptr_t )之間的轉換

  1. 當一個整數類型的值被轉換為_Bool以外的另一個整數類型時,如果該值可以用新類型表示,則不變。

  2. 否則,如果新類型是無符號的,則通過重復加或減一個新類型可以表示的最大值來轉換該值,直到該值在新類型的范圍內。

  3. 否則,新類型是有符號的,值不能在其中表示; 要么結果是實現定義的,要么引發實現定義的信號。


因此,如果我們從一個int值開始,並且實現提供了一個intptr_t類型sizeof(int) <= sizeof(intptr_t)INTPTR_MIN <= n && n <= INTPTR_MAX ,我們可以安全地將其轉換為intptr_t然后再轉換背部。

intptr_t可以轉換為void * ,然后轉換回相同的(1) (2) intptr_t值。

對於intvoid *之間的直接轉換,即使在提供的示例中,值 (42) 足夠小,不會導致未定義的行為,通常情況下也不相同。


我個人認為鏈接的 GLib 文檔中為這些類型轉換宏給出的原因很有爭議(重點是我的)

很多時候,GLib、GTK+ 和其他庫允許您以空指針的形式將“用戶數據”傳遞給回調。 有時你想傳遞一個整數而不是一個指針 您可以分配一個整數 [...] 但這很不方便,而且稍后必須釋放內存很煩人。

指針的大小始終至少為 32 位(在 GLib 打算支持的所有平台上)。 因此,您可以在指針值中存儲至少 32 位整數值。

我會讓讀者決定他們的方法是否比簡單的方法更有意義

#include <stdio.h>

void f(void *ptr)
{
    int n = *(int *)ptr;
    //      ^ Yes, here you may "pay" the indirection
    printf("%d\n", n);
}

int main(void)
{
    int n = 42;

    f((void *)&n);
}

(1) 我想引用Steve Jessop對這些類型的回答中的一段話

把這當作它所說的意思。 它沒有說明大小。
uintptr_t可能與void*大小相同。 它可能更大。 可以想象它會更小,盡管這樣的 C++ 實現方法有悖常理。 例如,在void*為 32 位,但僅使用 24 位虛擬地址空間的某些假設平台上,您可以擁有滿足要求的 24 位uintptr_t 我不知道為什么實現會這樣做,但標准允許這樣做。

(2) 實際上,標准明確提到了void* -> intptr_t/uintptr_t -> void*轉換,要求這些指針比較相等。 它沒有明確規定在intptr_t -> void* -> intptr_t的情況下,兩個整數值比較相等。 它只是在腳注67中提到“將指針轉換為整數或將整數轉換為指針的映射函數旨在與執行環境的尋址結構保持一致。”。

據我了解,代碼(void*)(long)42(void*)42 “更好”,因為它消除了gcc警告:

cast to pointer from integer of different size [-Wint-to-pointer-cast]

void*long具有相同大小但與int不同的環境中。 根據 C99,§6.4.4.1 ¶5:

整數常量的類型是其值可以在其中表示的相應列表中的第一個。

因此, 42被解釋為int ,如果將這個常量直接分配給void* (當sizeof(void*)!=sizeof(int) ),上面的警告會彈出,但每個人都想要干凈的編譯。 就是Glib 文檔指出的問題(問題?):它發生在某些系統上。


所以,兩個問題:

  1. 將整數分配給相同大小的指針
  2. 將整數分配給不同大小的指針

令我感到奇怪的是,即使這兩種情況在 C 標准和 gcc 實現說明(請參閱gcc 實現說明)中具有相同的狀態, gcc只顯示 2 的警告。

另一方面,很明顯,轉換為long並不總是解決方案(仍然,在大多數情況下,在現代 ABI sizeof(void*)==sizeof(long) ),有許多可能的組合取決於大小int , long , long longvoid* ,適用於64 位架構一般情況 這就是為什么 glib 開發人員試圖為指針找到匹配的整數類型並為mason構建系統相應地分配glib_gpi_castglib_gpui_cast 稍后,這些mason變量在此處用於以正確的方式生成這些轉換宏(對於基本 glib 類型,另請參閱此內容)。 最終,這些宏首先將一個整數轉換為另一個與void*大小相同的整數類型(這種轉換符合標准,沒有警告)用於目標架構。

這個擺脫警告的解決方案可以說是一個糟糕的設計,現在已經被intptr_tuintptr_t解決了,但它有可能是由於歷史原因而存在的: intptr_tuintptr_t是可用的,因為 C99和 Glib在 1998 年就開始了它的開發,所以他們為同一問題找到了自己的解決方案。 似乎有一些嘗試改變它:

GLib 依賴於有效 C99 工具鏈的各個部分,因此是時候盡可能使用 C99 整數類型,而不是像 1997 年那樣進行配置時發現。

然而沒有成功,它似乎從未進入主分支。


簡而言之,正如我所看到的,最初的問題已經從為什么這段代碼更好變成了為什么這個警告不好(並且讓它靜音是個好主意嗎? )。 后來已經回答了其他地方,而也可以幫助:

從指針轉換為整數(反之亦然)會導致代碼不可移植,並且可能會創建指向無效內存位置的意外指針。

但是,正如我上面所說,這條規則似乎不符合上述問題 1 的警告條件。 也許其他人可以對這個話題有所了解。

我對這種行為背后的基本原理的猜測是 gcc 決定在原始值以某種方式改變時發出警告,即使是微妙的。 正如gcc doc所說(強調我的):

如果指針表示小於整數類型,則從整數到指針的轉換丟棄最高有效位,如果指針表示大於整數類型,則根據整數類型的符號進行擴展,否則位不變

因此,如果大小匹配,則位沒有變化(沒有擴展、沒有截斷、沒有填充零)並且不會拋出警告。

此外, [u]intptr_t只是適當限定整數typedef :在將[u]intptr_t分配給void*時拋出警告是沒有[u]intptr_t ,因為這確實是它的目的。 如果規則適用於[u]intptr_t ,則它必須適用於typedef ed 整數類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM