[英]When is an array name or a function name 'converted' into a pointer ? (in C)
1)誤解:
每當在 C 語言中聲明數組時,都會隱式創建指向數組第一個元素的指針(數組的名稱)。 (是嗎?我不這么認為!)
此頁面的前兩行(盡管我不確定信息的正確性)表示相同。
正如我們所見,當我們聲明一個數組時,會為數組的單元格分配一個連續的內存塊,並且還會分配並初始化一個指針單元格(適當類型的)以指向數組的第一個單元格。
但是當我輸出包含在該指針中的地址和該指針的地址時,它們結果是相同的。 所以,我認為畢竟沒有創建指針。
任何人都可以詳細解釋編譯器何時決定將數組名稱轉換為指針,以及為什么?
PS:請解釋相同的功能。 同樣在這個鏈接中,給出了一個例子,說對於函數int square(int,int)
,任何square
、 &square
、 *square
、 **square
指的是同一個函數指針。 你可以解釋嗎?
編輯:代碼片段
int fruits[10];
printf("Address IN constant pointer is %p\n", fruits);
printf("Address OF constant pointer is %p\n", &fruits);
輸出 :
Address IN constant pointer is 0xbff99ca8
Address OF constant pointer is 0xbff99ca8
數組類型的表達式被隱式轉換為指向數組對象的第一個元素的指針,除非它是:
&
運算符的操作數;sizeof
的操作數; 要么第三種情況的一個例子是:
char arr[6] = "hello";
"hello"
是一個數組表達式,類型為char[6]
(5 加 1 表示'\\0'
終止符)。 它不會轉換為地址; "hello"
的完整 6 字節值被復制到數組對象arr
。
另一方面,在這方面:
char *ptr = "hello";
數組表達式"hello"
"decays" 指向指向'h'
指針,該指針值用於初始化指針對象ptr
。 (它真的應該是const char *ptr
,但這是一個附帶問題。)
函數類型的表達式(例如函數名)被隱式轉換為指向該函數的指針,除非它是:
&
運算符的操作數; 要么sizeof
的操作數( sizeof function_name
是非法的,不是指針的大小)。而已。
在這兩種情況下,都不會創建指針對象。 該表達式被轉換為(“衰減”為)一個指針值,也稱為地址。
(這兩種情況下的“轉換”不是像強制轉換運算符所指定的那樣的普通類型轉換。它不獲取操作數的值並使用它來計算結果的值,就像一個int
-到- float
轉換,而是陣列或功能類型的表達被“轉化”在編譯時指針類型的表達式。在我的“調整”會比“轉化”更清晰的意見的話。)
請注意,數組索引運算符[]
和函數調用“operator” ()
都需要一個指針。 在像func(42)
這樣的普通函數調用中,函數名稱func
“衰減”為指向函數的指針,然后在調用中使用該指針。 (這種轉換實際上不需要在生成的代碼中執行,只要函數調用正確。)
函數規則有一些奇怪的后果。 在大多數情況下,表達式func
被轉換為指向函數func
的指針。 在&func
, func
不會轉換為指針,而是&
產生函數的地址,即指針值。 在*func
, func
被隱式轉換為指針,然后*
取消引用它以產生函數本身,然后(在大多數情況下)轉換為指針。 在****func
,這種情況反復發生。
(C11 標准草案說數組還有另一個例外,即當數組是新的_Alignof
運算符的操作數時。這是草案中的一個錯誤,在最終發布的 C11 標准中已更正; _Alignof
只能應用於括號中的類型名稱,而不是表達式。)
數組的地址及其第一個成員的地址:
int arr[10];
&arr; /* address of entire array */
&arr[0]; /* address of first element */
是相同的內存地址,但它們的類型不同。 前者是整個數組對象的地址,類型為int(*)[10]
(指向 10 個int
數組的指針); 后者是int*
類型。 這兩種類型不兼容(例如,您不能合法地將int*
值分配給int(*)[10]
對象),並且指針算術對它們的行為不同。
有一個單獨的規則,即陣列或功能類型的聲明的函數參數是在編譯時(未轉化的)的指針參數調整。 例如:
void func(int arr[]);
完全等同於
void func(int *arr);
這些規則(數組表達式的轉換和數組參數的調整)結合起來,對 C 中數組和指針之間的關系造成了很大的混淆。
comp.lang.c FAQ 的第 6 節很好地解釋了細節。
這方面的權威來源是 ISO C 標准。 N1570 (1.6 MB PDF) 是 2011 標准的最新草案; 這些轉換在第 6.3.2.1 節的第 3(數組)和第 4(函數)段中指定。 該草案錯誤地引用了_Alignof
,實際上並不適用。
順便說一句,您示例中的printf
調用是完全不正確的:
int fruits[10];
printf("Address IN constant pointer is %p\n",fruits);
printf("Address OF constant pointer is %p\n",&fruits);
%p
格式需要一個void*
類型的參數。 如果類型的指針int*
和int(*)[10]
有相同的表示為void*
和以同樣的方式傳遞參數,如對於大多數實現的情況下,它可能的工作,但它不能保證。 您應該將指針顯式轉換為void*
:
int fruits[10];
printf("Address IN constant pointer is %p\n", (void*)fruits);
printf("Address OF constant pointer is %p\n", (void*)&fruits);
那么為什么要這樣做呢? 問題是數組在某種意義上是 C 中的二等公民。您不能在函數調用中按值傳遞數組作為參數,也不能將它作為函數結果返回。 要使數組有用,您需要能夠對不同長度的數組進行操作。 單獨的用於char[1]
、 char[2]
、 char[3]
等的strlen
函數(所有這些都是不同的類型)將是不可能的。 因此,數組是通過指向其元素的指針來訪問和操作的,指針算法提供了一種遍歷這些元素的方法。
如果數組表達式沒有衰減為指針(在大多數情況下),那么您對結果無能為力。 C 源自早期的語言(BCPL 和 B),它們甚至不一定區分數組和指針。
其他語言能夠將數組作為一流類型處理,但這樣做需要額外的功能,這些功能不會“符合 C 的精神”,而 C 仍然是一種相對低級的語言。
我不太確定以這種方式處理函數的理由。 確實沒有函數類型的值,但該語言可能需要一個函數(而不是函數指針)作為函數調用的前綴,需要一個顯式的*
操作符來進行間接調用: (*funcptr)(arg)
。 能夠省略*
是一種方便,但不是很大。 這可能是歷史慣性和對數組處理的一致性的結合。
您問題第一部分的鏈接頁面中給出的描述肯定是完全不正確的。 那里沒有指針,無論是否恆定。 您可以在@KeithThompson 的回答中找到對數組/函數行為的詳盡解釋。
最重要的是,添加(作為旁注)作為兩部分對象實現的數組 - 一個指向獨立無名內存塊的命名指針 - 並不完全是幻想。 它們以那種特定的形式存在於 C 語言的前身——B 語言中。 最初它們從 B 轉移到 C 完全沒有改變。 您可以在 Dennis Ritchie 的“ The Development of the C Language ”文檔中閱讀到它(參見“Embryonic C”部分)。
但是,正如該文檔中所述,這種數組實現與 C 語言的一些新特性(如結構類型)不兼容。 在 struct 對象中擁有由兩部分組成的數組會將這些對象轉換為具有非平凡構造的更高級別的實體。 這也會使它們與原始內存操作(如memcpy
等)不兼容。 這些考慮是將數組從兩部分對象重新設計為當前的單部分形式的原因。 而且,正如您在該文檔中所讀到的,重新設計是在考慮到 B 樣式數組的向后兼容性的情況下進行的。
所以,首先,這就是為什么許多人對 C 風格數組的行為感到困惑,認為那里隱藏着一個指針。 現代 C 數組的行為是專門為模仿/維持這種錯覺而設計的。 其次,一些古老的文檔可能仍然包含那個“胚胎”時代的遺留物(盡管看起來你鏈接的文檔不應該是其中之一。)
有一種更好的方式來思考它。 數組類型的表達式(包括:數組名、數組指針的解引用、二維數組的下標等)就是——數組類型的表達式。 它不是指針類型的表達式。 但是,如果在需要指針的上下文中使用該語言,則該語言提供了從數組類型表達式到指針類型表達式的隱式轉換。
你不需要記住,哦,它被轉換為一個指針“除了” sizeof
和&
等。你只需要考慮表達式的上下文。
例如,考慮何時嘗試將數組表達式傳遞給函數調用。 根據 C 標准,函數參數不能是數組類型。 如果對應的參數是指針類型(為了編譯必須是指針類型),那么編譯器看到,哦,它想要一個指針,所以它應用數組表達式到指針類型的轉換。
或者,如果您使用帶有解引用運算符*
或算術運算符+
-
或下標運算符[]
的數組表達式; 這些運算符都對指針進行操作,因此編譯器再次看到並應用轉換。
當您嘗試分配數組表達式時,好吧,在 C 中,數組類型是不可分配的,因此它可以編譯的唯一方法是將其分配給指針類型,在這種情況下,編譯器再次看到它需要一個指針,並應用轉換。
當您將它與sizeof
和&
一起使用時,這些上下文對數組具有固有的意義,因此編譯器不會費心應用轉換。 這些被視為數組到指針轉換的“例外”的唯一原因很簡單,C 中的所有其他表達式上下文(如您在上面的示例中所見)對於數組類型(數組類型在 C 中如此殘缺),而這些是唯一“留下”的類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.