[英]What is the difference between char str[] and char *str as function parameters?
[英]What is the difference between char*str={“foo”,…} and char str[][5]={“foo”,…} array definitions?
案例1:我寫的時候
char*str={"what","is","this"};
那么str[i]="newstring";
是有效的,而str[i][j]='j';
是無效的。
案例2:我寫的時候
char str[][5]={"what","is","this"};
那么str[i]="newstring";
無效,而str[i][j]='J';
已驗證。
為什么會這樣? 我是一個初學者,在閱讀其他答案后已經非常困惑。
首先 :一個建議:請閱讀有關數組不是指針,反之亦然 !
也就是說,為了啟發這種特殊情況,
在第一種情況下 ,
char*str={"what","is","this"};
不會做你認為它做的事情。 這是一種約束違規,需要根據章節§6.7.9/ P2從任何符合要求的C實現進行診斷:
初始化程序不應嘗試為未初始化的實體中包含的對象提供值。
如果你啟用警告,你( 至少 )會看到
警告:標量初始化程序中的多余元素
char*str={"what","is","this"};
但是,打開嚴格一致性的(ny)編譯器應該拒絕編譯代碼。 如果編譯器選擇編譯並生成二進制文件,那么行為就不會影響C語言的定義范圍,這取決於編譯器的實現(因此可以有很大的不同)。
在這種情況下,編譯器決定此語句在功能上僅與char*str= "what";
所以,這里str
是一個指向char
的指針,指向一個字符串文字 。 您可以重新分配指針,
str="newstring"; //this is valid
但是,像一個聲明
str[i]="newstring";
將無效 ,因為在這里,嘗試轉換指針類型並將其存儲到char
類型中,其中類型不兼容。 在這種情況下,編譯器應該拋出有關無效轉換的警告。
此后,聲明如
str[i][j]='J'; // compiler error
在語法上是無效的,因為你在不是“指向完整對象類型的指針”的東西上使用了Array subscripting []
運算符,就像
str[i][j] = ... ^^^------------------- cannot use this ^^^^^^ --------------------- str[i] is of type 'char', not a pointer to be used as the operand for [] operator.
另一方面,在第二種情況下 ,
str
是一個數組數組。 你可以改變單個數組元素,
str[i][j]='J'; // change individual element, good to go.
但你不能分配給一個數組。
str[i]="newstring"; // nopes, array type is not an lvalue!!
最后, 考慮你打算寫(如評論中所示)
char* str[ ] ={"what","is","this"};
在第一種情況下,數組的邏輯相同。 這使str
成為一個指針數組。 所以,數組成員是可分配的,所以,
str[i]="newstring"; // just overwrites the previous pointer
完全沒問題。 但是,存儲為數組成員的指針是指向字符串文字的指針,因此出於上述原因,當您要修改屬於字符串文字的內存中的一個元素時,可以調用未定義的行為 。
str[i][j]='j'; //still invalid, as above.
內存布局不同:
char* str[] = {"what", "is", "this"};
str
+--------+ +-----+
| pointer| ---> |what0|
+--------+ +-----+ +---+
| pointer| -------------> |is0|
+--------+ +---+ +-----+
| pointer| ----------------------> |this0|
+--------+ +-----+
在這種內存布局中, str
是指向各個字符串的指針數組。 通常,這些單獨的字符串將駐留在靜態存儲中,嘗試修改它們是錯誤的。 在圖中,我使用0
來表示終止空字節。
char str[][5] = {"what", "is", "this"};
str
+-----+
|what0|
+-----+
|is000|
+-----+
|this0|
+-----+
在這種情況下, str
是位於堆棧上的連續的2D字符數組。 在初始化數組時,字符串被復制到該存儲區中,並且用零字節填充各個字符串以使該數組具有規則形狀。
這兩種內存布局從根本上是不相容的。 您不能將任何一個傳遞給期望指向另一個的函數。 但是,訪問單個字符串是兼容的。 當你寫str[1]
,你得到一個char*
到包含字節is0
的內存區域的第一個字符,即一個C字符串。
在第一種情況下,很明顯這個指針只是從內存加載。 在第二種情況下,指針是通過數組指針衰減創建的: str[1]
實際上表示一個恰好五個字節( is000
)的數組,它幾乎在所有上下文中立即衰減成指向其第一個元素的指針。 但是,我認為對數組指針衰減的完整解釋超出了這個答案的范圍。 如果你好奇,谷歌數組指針會衰減。
首先,您定義一個變量,該變量是指向char
的指針,該變量通常僅用作單個字符串 。 它初始化指針指向字符串文字"what"
。 編譯器還應該抱怨列表中有太多的初始值設定項。
第二個定義使str
成為一個包含五個char
的三個數組的數組。 也就是說,它是由三個五個字符的字符串組成的數組。
稍微不同,可以看到這樣的事情:
對於第一種情況:
+-----+ +--------+ | str | --> | "what" | +-----+ +--------+
而你的第二個
+--------+--------+--------+ | "what" | "is" | "this" | +--------+--------+--------+
另請注意,對於第一個版本,使用指向單個字符串的指針,表達式str[i] = "newstring"
也應該導致警告,因為您嘗試將指針指向單個char
元素 str[i]
。
這個賦值在第二個版本中也是無效的,但是由於另一個原因: str[i]
是一個數組 (由五個char
元素組成),你不能分配給一個數組,只能復制到它。 所以你可以嘗試做strcpy(str[i], "newstring")
並且編譯器不會抱怨。 但這是錯誤的 ,因為你試圖將10個字符(記住終結符)復制到5個字符的數組中,這將寫出超出界限導致未定義的行為 。
在第一個聲明中
char *str={"what","is","this"};
聲明str
指向char
的指針並且是標量。 標准說
6.7.9初始化(p11):
標量的初始值設定項應為單個表達式,可選擇用大括號括起來 。 [...]
也就是說,標量類型可以使用括號封閉的初始化器,但只有一個表達式,但是如果有的話
char *str = {"what","is","this"}; // three expressions in brace enclosed initializer
編譯器是如何處理它的。 請注意,其余初始化程序會發生什么錯誤 。 確認編制者應給出診斷信息。
[Warning] excess elements in scalar initializer
5.1.1.3診斷(P1):
如果預處理轉換單元或轉換單元包含違反任何語法規則或約束的情況,則符合要求的實現應生成至少一條診斷消息(以實現定義的方式標識),即使該行為也明確指定為未定義或實現 - 定義
你聲稱“ str[i]="newstring";
有效而str[i][j]='j';
無效。 ”
str[i]
是char
類型,只能保存char
數據類型。 分配"newstring"
( char *
)是無效的。 語句str[i][j]='j';
由於下標運算符只能應用於數組或指針數據類型,因此無效。
你可以使str[i]="newstring";
通過將str
聲明為char *
數組來工作
char *str[] = {"what","is","this"};
在這種情況下, str[i]
是char *
類型,並且可以為其分配字符串文字但是修改字符串文字str[i]
指向將調用未定義的行為。 那說你不能做str[0][0] = 'W'
。
片段
char str[][5]={"what","is","this"};
將str
聲明為char
的數組數組。 str[i]
實際上是一個數組,並且數組是不可修改的左值,因此您不能將它們用作賦值運算符的左操作數。 這使得str[i]="newstring";
無效。 而str[i][j]='J';
因為可以修改數組的元素。
僅僅因為你說其他答案令我困惑,讓我們先看一個更簡單的例子來看看發生了什么
char *ptr = "somestring";
這里"somestring"
是一個字符串文字 ,存儲在內存的只讀數據部分 。 ptr
是一個指針(就像在代碼的同一部分中的其他變量一樣分配),它指向該分配的內存的第一個字節。
因此cnosider這兩個陳述
char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a'; //statement 2 error
語句1執行完全有效的操作(將1指針指向另一個),但語句2不是有效操作(嘗試寫入只讀位置)。
另一方面,如果我們寫:
char ptr[] = "somestring";
這里ptr實際上不是指針,而是數組的名稱(與指針不同,它不占用內存中的額外空間)。 它分配"somestring"
(不是只讀)所需的相同字節數,就是這樣。
因此,請考慮相同的兩個陳述和一個額外的陳述
char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a'; //statement 2 OK
ptr = "someotherstring" //statement 3 error
語句1執行完全有效的操作(將數組名稱指定給指針,數組名稱返回第1個字節的地址),語句2也有效,因為內存不是只讀的。
語句3不是有效的操作,因為這里ptr不是指針,它不能指向其他一些內存位置。
現在在這段代碼中,
char **str={"what","is","this"};
*str
是一個指針( str[i]
與*(str+i)
)
但是在這段代碼中
char str[][] = {"what", "is", "this"};
str[i]
不是指針。 它是數組的名稱。
與上述相同的是。
要消除混淆,您必須正確理解指針,數組和初始化器。 C編程初學者中常見的誤解是數組等同於指針。
數組是相同類型的項的集合。 考慮以下聲明:
char arr[10];
該數組包含10個元素,每個元素都是char
類型。
初始化列表可用於以方便的方式初始化陣列。 下面使用初始化列表的相應值初始化數組元素:
char array[10] = {'a','b','c','d','e','f','g','h','i','\0'};
數組不可分配,因此初始化程序列表的使用僅在數組聲明時有效。
char array[10];
array = {'a','b','c','d','e','f','g','h','i','\0'}; // Invalid...
char array1[10];
char array2[10] = {'a','b','c','d','e','f','g','h','i','\0'};
array1 = array2; // Invalid...; You cannot copy array2 to array1 in this manner.
在聲明數組之后,對數組成員的賦值必須通過數組索引運算符或其等價運算符。
char array[10];
array[0] = 'a';
array[1] = 'b';
.
.
.
array[9] = 'i';
array[10] = '\0';
循環是為數組成員賦值的常用且方便的方法:
char array[10];
int index = 0;
for(char val = 'a'; val <= 'i'; val++) {
array[index] = val;
index++;
}
array[index] = '\0';
char
數組可以通過字符串文字初始化,字符串文字是常量空終止的char
數組:
char array[10] = "abcdefghi";
但是以下內容無效:
char array[10];
array = "abcdefghi"; // As mentioned before, arrays are not assignable
現在,讓我們來指點......指針是可以存儲另一個變量的地址的變量,通常是相同類型的。
請考慮以下聲明:
char *ptr;
這聲明了一個char *
類型的變量,一個char
指針。 也就是說,指針可以指向char
變量。
與數組不同,指針是可分配的。 因此以下內容有效:
char var;
char *ptr;
ptr = &var; // Perfectly Valid...
由於指針不是數組,因此指針只能分配一個值。
char var;
char *ptr = &var; // The address of the variable `var` is stored as a value of the pointer `ptr`
回想一下,必須為指針分配一個值,因此以下內容無效,因為初始值設定項的數量不止一個:
char *ptr = {'a','b','c','d','\0'};
這是一個約束違規,但您的編譯器可能只是將'a'
分配給ptr
而忽略其余的。 但即使這樣,編譯器也會警告你,因為像'a'
這樣的字符文字默認具有int
類型,並且與ptr
的類型不兼容,即char *
。
如果在運行時已取消引用此指針,則會導致訪問無效內存的運行時錯誤,從而導致程序崩潰。
在你的例子中:
char *str = {"what", "is", "this"};
再次,這是一個約束違規,但您的編譯器可能會分配字符串what
到str
並忽略其余的,並只顯示一個警告:
warning: excess elements in scalar initializer
。
現在,我們如何消除關於指針和數組的混淆:在某些上下文中,數組可能會衰減為指向數組第一個元素的指針。 因此以下內容有效:
char arr[10];
char *ptr = arr;
通過在賦值表達式中使用數組名稱arr
作為rvalue
,數組衰減到指向它的第一個元素的指針,這使得前一個表達式等效於:
char *ptr = &arr[0];
請記住, arr[0]
的類型為char
,而&arr[0]
的地址類型為char *
,它與變量ptr
兼容。
回想一下,字符串文字是常量空終止的char
數組 ,因此以下表達式也是有效的:
char *ptr = "abcdefghi"; // the array "abcdefghi" decays to a pointer to the first element 'a'
現在,在你的情況下, char str[][5] = {"what","is","this"};
是一個包含3個數組的數組,每個數組包含5個元素。
由於數組不可分配, str[i] = "newstring";
因為str[i]
是一個數組,所以無效,但str[i][j] = 'j';
是有效的,因為str[i][j]
是一個數組元素,它本身不是一個數組,並且是可賦值的。
首先
char*str={"what","is","this"};
甚至不是有效的C代碼1) ,所以討論它不是很有意義 。 出於某種原因,gcc編譯器只允許此代碼發出警告。 不要忽略編譯器警告。 使用gcc時,請確保始終使用-std=c11 -pedantic-errors -Wall -Wextra
。
當遇到這個非標准代碼時,gcc似乎要做的就是把它當作你寫的char*str={"what"};
。 這反過來與char*str="what";
。 這絕不是C語言的保證。
str[i][j]
嘗試間接指針兩次,即使它只有一個間接級別,因此會出現編譯器錯誤。 它打字的意義不大
int array [3] = {1,2,3}; int x = array[0][0];
。
至於char* str = ...
和char str[] = ...
之間的區別 ,請參閱常見問題:char s []和char * s有什么區別? 。
關於char str[][5]={"what","is","this"};
case,它創建一個數組數組(2D數組)。 最內層維度設置為5,最外層維度由編譯器自動設置,具體取決於程序員提供的初始化程序數。 在這種情況下3,所以代碼相當於char[3][5]
。
str[i]
給出數組數組中的數組i
。 您無法在C中分配數組,因為這就是語言的設計方式。 此外,對於字符串這樣做是不正確的, FAQ:如何正確分配新的字符串值?
1)這是違反C11 6.7.9 / 2的約束。 另見6.7.9 / 11。
當我寫作
char*str={"what","is","this"};
那么str[i]="newstring";
是有效的,而str[i][j]='j';
是無效的。
第二部分
>> char*str={"what","is","this"};
在這個語句中, str
是一個指向char
類型的指針。 編譯時,您必須在此語句中收到警告消息 :
warning: excess elements in scalar initializer
char*str={"what","is","this"};
^
警告的原因是 - 您為標量提供了多個初始值設定項。
[ 算術類型和指針類型統稱為標量類型。 ]
str
是一個標量,來自C標准#6.7.9p11 :
標量的初始值設定項應為單個表達式,可選擇用大括號括起來。 ..
此外,為標量提供多個初始化程序是未定義的行為 。
來自C標准#J.2未定義的行為 :
標量的初始值設定項既不是單個表達式,也不是括在括號中的單個表達式
由於它是按照標准的未定義行為,因此沒有必要進一步討論它 。 討論第 I.II 部分和第III部分的假設 - char *str="somestring"
,只是為了更好地理解char *
類型。
似乎你想要創建一個指向字符串的指針數組。 在談到這兩個案例之后,我在這篇文章的下面添加了一個關於字符串指針數組的簡要說明。
第一部分.II
>> then str[i]="newstring"; is valid
不 ,這是無效的 。
同樣,由於轉換不兼容,編譯器必須在此語句上發出警告消息 。
因為str
是指向char
類型的指針。 因此, str[i]
是在一個字符i
地方過去對象通過指向str
[ str[i] --> *(str + i)
]。
"newstring"
是一個字符串文字和一個字符串文字衰變成一個指針,除了用於初始化一個類型為char *
的數組時,你試圖將它分配給一個char
類型。 因此編譯器將其報告為警告。
第III部分
>> whereas str[i][j]='j'; is invalid.
是的,這是無效的。
[]
(下標運算符)可以與數組或指針操作數一起使用。
str[i]
是一個字符, str[i][j]
表示你在char
操作數上使用[]
是無效的。 因此編譯器將其報告為錯誤。
當我寫作
char str[][5]={"what","is","this"};
那么str[i]="newstring";
無效,而str[i][j]='J';
已驗證。
第二部分.I
>> char str[][5]={"what","is","this"};
這絕對是正確的。 這里, str
是2D陣列。 根據初始化程序的數量,編譯器將自動設置第一個維度。 在這種情況下, str[][5]
的內存視圖將是這樣的:
str
+-+-+-+-+-+
str[0] |w|h|a|t|0|
+-+-+-+-+-+
str[1] |i|s|0|0|0|
+-+-+-+-+-+
str[2] |t|h|i|s|0|
+-+-+-+-+-+
基於初始化列表,將初始化2D陣列的各個元素,並將其余元素設置為0
。
第II.II部分
>> then str[i]="newstring"; is not valid
是的,這是無效的。
str[i]
是一維數組。
根據C標准,數組不是可修改的左值。
來自C標准#6.3.2.1p1 :
左值是一個表達式(對象類型不是void)可能指定一個對象; 64)如果左值在評估時沒有指定對象,則行為是未定義的。 當一個對象被稱為具有特定類型時,該類型由用於指定該對象的左值指定。 可修改的左值是一個左值,它沒有數組類型,沒有不完整的類型,沒有const限定類型,如果是結構或聯合,則沒有任何成員(包括遞歸地,任何成員)或具有常量類型的所有包含的聚合或聯合的元素。
此外,數組名稱轉換為指向數組對象的初始元素的指針,除非它是sizeof運算符,_Alignof運算符或一元&運算符的操作數。
來自C標准#6.3.2.1p3 :
除非它是sizeof運算符,_Alignof運算符或一元&運算符的操作數,或者是用於初始化數組的字符串文字,否則將具有類型''數組類型''的表達式轉換為表達式輸入''指向類型'的指針,指向數組對象的初始元素,而不是左值。
由於str
已經初始化,並且當你將一些其他字符串文字分配給第 i
個 str
數組時,字符串文字轉換為一個指針,這使得賦值不兼容,因為你有左數字類型的char
數組和rvalue類型為char *
。 因此編譯器將其報告為錯誤。
第II.III部分
>> whereas str[i][j]='J'; is valid.
是的,只要i
和j
是給定數組str
有效值,這就有效。
str[i][j]
的類型為char
,因此您可以為其指定一個字符。 請注意,C不檢查數組邊界並且訪問數組越界是未定義的行為,包括 - 它可能偶然地完成程序員的意圖或分段錯誤或者無聲地生成錯誤結果或任何事情都可能發生。
假設在案例1中 ,您想要創建一個指向字符串的指針數組。
它應該是這樣的:
char *str[]={"what","is","this"};
^^
str
的內存視圖將是這樣的:
str
+----+ +-+-+-+-+--+
str[0]| |--->|w|h|a|t|\0|
| | +-+-+-+-+--+
+----+ +-+-+--+
str[1]| |--->|i|s|\0|
| | +-+-+--+
+----+ +-+-+-+-+--+
str[2]| |--->|t|h|i|s|\0|
| | +-+-+-+-+--+
+----+
"what"
, "is"
和"this"
是字符串文字。
str[0]
, str[1]
和str[2]
是指向相應字符串文字的指針,您也可以將它們指向其他字符串。
所以,這非常好:
str[i]="newstring";
假設i
是1,那么str[1]
指針現在指向字符串文字"newstring"
:
+----+ +-+-+-+-+-+-+-+-+-+--+
str[1]| |--->|n|e|w|s|t|r|i|n|g|\0|
| | +-+-+-+-+-+-+-+-+-+--+
+----+
但你不應該這樣做 :
str[i][j]='j';
(假設i=1
且j=0
,所以str[i][j]
是第二個字符串的第一個字符)
根據標准嘗試修改字符串文字導致未定義的行為,因為它們可能存儲在只讀存儲中或與其他字符串文字組合。
從C標准#6.4.5p7 :
如果這些數組的元素具有適當的值,則這些數組是否不同是未指定的。 如果程序試圖修改此類數組,則行為未定義。
C語言中沒有本機字符串類型。 在C語言中,字符串是以空字符結尾的字符數組 。 你應該知道數組和指針之間的區別。
我建議你閱讀以下內容,以便更好地理解數組,指針,數組初始化:
情況1 :
char*str={"what","is","this"};
首先上述聲明無效 ,請正確閱讀警告。 str
是單指針,它可以指向single
char數組,而不是multiple
char數組。
bounty.c:3:2:警告:標量初始值設定項中的多余元素[默認啟用]
str
是一個char pointer
,它存儲在RAM的section
部分,但是它的contents
存儲在code(Can't modify the content
RAM code(Can't modify the content
部分,因為str
是用string(in GCC/linux)
初始化的string(in GCC/linux)
。
正如你所說str [i] =“newstring”; 是有效的,而str [i] [j] ='j'; 是無效的。
str= "new string"
沒有導致修改代碼/只讀部分,這里你只是為str
分配new address
,這就是為什么它有效但是
*str='j'
或str[0][0]='j'
無效,因為您在這里修改只讀部分 ,嘗試更改str
第一個字母。
案例2:
char str[][5]={"what","is","this"};
這里str
是2D
數組,即str
和str[0],str[1],str[2]
本身存儲在RAM
stack section
,這意味着你可以改變每個str[i]
內容。
str[i][j]='w';
它是有效的,因為您正在嘗試堆疊可能的部分內容。 但
str[i]= "new string";
這是不可能的,因為str[0]
本身的數組和數組是const指針(不能改變地址) ,你不能分配新的地址。
簡單地說在第一種情況下 str="new string"
是valid
因為str
是pointer
,而不是array
,在第二種情況下 str[0]="new string"
not valid
因為str
是array
而不是pointer
。
我希望它有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.