簡體   English   中英

C字符串混亂

[英]C strings confusion

我現在正在學習C並且對字符數組 - 字符串感到困惑。

char name[15]="Fortran";

沒問題 - 它的數組可以容納(最多?)15個字符

char name[]="Fortran";

C計算我的字符數,所以我沒有 - 整潔!

char* name;

好的。 現在怎么辦? 我所知道的是,這可以容納后來分配的大量字符(例如:通過用戶輸入),但是

  • 為什么他們稱之為char指針? 我知道指針作為變量的引用
  • 這是“借口”嗎? 這是否找到除char *之外的任何其他用途?
  • 這究竟是什么? 它是指針嗎? 你如何正確使用它?

提前謝謝,喇嘛

我認為這可以用這種方式解釋,因為一張圖片勝過千言萬語......

我們將從char name[] = "Fortran" ,這是一個字符數組,長度在編譯時已知,確切地說是7,對吧? 錯誤! 它是8,因為'\\ 0'是一個空終止字符,所有字符串都必須有。

char name[] = "Fortran";
+======+     +-+-+-+-+-+-+-+--+
|0x1234|     |F|o|r|t|r|a|n|\0|
+======+     +-+-+-+-+-+-+-+--+

在鏈接時,編譯器和鏈接器為符號name提供了0x1234的內存地址。 使用下標運算符,例如name[1] ,編譯器知道如何計算內存中偏移處的字符,0x1234 + 1 = 0x1235,並且它確實是'o'。 這是很簡單的,此外,與ANSI C標准,一個大小char數據類型是1個字節,其可以解釋運行時可以如何獲得該語義值name[cnt++]假設cntint埃格爾並具有例如,值為3,運行時自動向上逐步遞增,從零開始計數,偏移量的值為“t”。 到目前為止這很簡單。

如果name[12]被執行會怎么樣? 好吧,代碼會崩潰,或者你會得到垃圾,因為數組的邊界是從索引/偏移0(0x1234)到8(0x123B)。 之后的任何東西都不屬於name變量,這將被稱為緩沖區溢出!

內存中的name地址為0x1234,如示例所示,如果您這樣做:

printf("The address of name is %p\n", &name);

Output would be:
The address of name is 0x00001234

為了簡潔和保持示例,內存地址是32位,因此您可以看到額外的0。 很公平? 對,讓我們繼續吧。

現在指向... char *name是指向char類型的指針....

編輯:我們將它初始化為NULL如圖所示感謝Dan指出小錯誤...

char *name = (char*)NULL;
+======+     +======+ 
|0x5678| ->  |0x0000|    ->    NULL
+======+     +======+

在編譯/鏈接時, name不指向任何內容,但是具有符號name的編譯/鏈接時間地址(0x5678),實際上它是NULLname的指針地址是未知的,因此是0x0000。

現在,請記住這是至關重要的,符號的地址在編譯/鏈接時是已知的,但在處理任何類型的指針時指針地址是未知的

假設我們這樣做:

name = (char *)malloc((20 * sizeof(char)) + 1);
strcpy(name, "Fortran");

我們調用malloc為20個字節分配一個內存塊,不,它不是21,我加上1的大小的原因是'\\ 0'nul終止字符。 假設在運行時,給出的地址是0x9876,

char *name;
+======+     +======+          +-+-+-+-+-+-+-+--+
|0x5678| ->  |0x9876|    ->    |F|o|r|t|r|a|n|\0|
+======+     +======+          +-+-+-+-+-+-+-+--+

所以當你這樣做時:

printf("The address of name is %p\n", name);
printf("The address of name is %p\n", &name);

Output would be:
The address of name is 0x00005678
The address of name is 0x00009876

現在,這就是“ 陣列和指針相同的幻覺在這里發揮作用

當我們這樣做時:

char ch = name[1];

運行時會發生什么:

  1. 查找符號name的地址
  2. 獲取該符號的內存地址,即0x5678。
  3. 在該地址處,包含另一個地址,指向存儲器的指針地址並獲取它,即0x9876
  4. 根據下標值1獲取偏移量並將其添加到指針地址,即0x9877,以檢索該存儲器地址的值,即“o”並分配給ch

上面的內容對於理解這種區別至關重要,數組和指針之間的區別在於運行時如何使用指針獲取數據,還有一個額外的取向間接。

請記住T類型的數組總是會衰減為 T類型 的第一個元素的指針

當我們這樣做時:

char ch = *(name + 5);
  1. 查找符號name的地址
  2. 獲取該符號的內存地址,即0x5678。
  3. 在該地址處,包含另一個地址,指向存儲器的指針地址並獲取它,即0x9876
  4. 獲取基於值5的偏移量並將其添加到指針地址,即0x987A以檢索該存儲器地址處的值,即“r”並分配給ch

順便說一下,你也可以對字符數組這樣做...

此外,通過在數組的上下文中使用下標運算符,即char name[] = "..."; name[subscript_value]實際上與*(name + subscript_value)相同。

name[3] is the same as *(name + 3)

因為表達式*(name + subscript_value)可交換的 ,所以相反,

*(subscript_value + name) is the same as *(name + subscript_value)

因此,這解釋了為什么在上面的一個答案中你可以這樣寫( 盡管如此,即使它是非常合理的,也不推薦這種做法!

3[name]

好的,我如何獲得指針的值? 這就是*的用途,假設指針name指針內存地址為0x9878,再次參考上面的例子,這就是它的實現方式:

char ch = *name;

這意味着,獲取0x9878的內存地址所指向的值,現在ch將具有值'r'。 這稱為解除引用。 我們只是取消引用一個name指針來獲取值並將其分配給ch

此外,編譯器知道sizeof(char)為1,因此您可以像這樣執行指針遞增/遞減操作

*name++;
*name--;

指針會自動向上/向下逐步上升/下降。

當我們這樣做時,假設指針內存地址為0x9878:

char ch = *name++;

* name的值是什么,地址是什么,答案是, *name現在包含't'並將其分配給ch ,指針存儲器地址是0x9879。

在這里你必須要小心,與前面關於內存邊界的內容相同的原則和精神(參見上文中“如果名稱[12]被執行時會發生什么”)結果將是相同的,即代碼崩潰和燒傷!

現在,如果我們通過以name作為參數調用C函數free來解除分配name所指向的內存塊,即free(name)

+======+     +======+ 
|0x5678| ->  |0x0000|    ->    NULL
+======+     +======+

是的,內存塊被釋放並傳回運行時環境,供另一個即將發布的malloc代碼執行使用。

現在,這是分段錯誤的常用符號發揮作用的地方,因為name不指向任何東西,當我們取消引用它時會發生什么,即

char ch = *name;

是的,代碼將崩潰並以“分段故障”刻錄,這在Unix / Linux下很常見。 在Windows下,將出現一個對話框,其中包含“不可恢復的錯誤”或“應用程序發生錯誤,您是否希望將報告發送給Microsoft?”....如果指針不是malloc d並且任何取消引用它的嘗試都會保證崩潰和燃燒。

另外:記住這一點,對於每個malloc都有一個相應的free ,如果沒有相應的free ,你有一個內存泄漏,其中分配了內存但沒有釋放。

而且你有它,這就是指針如何工作以及數組如何與指針不同,如果你正在閱讀一本說它們相同的教科書,那就撕下那個頁面然后撕掉它! :)

我希望這有助於你理解指針。

那是一個指針。 這意味着它是一個在內存中保存地址的變量。 它“指向”另一個變量。

它實際上不能 - 本身 - 持有大量的字符。 它本身只能在內存中保存一個地址。 如果在創建時為其分配字符,它將為這些字符分配空間,然后指向該地址。 你可以這樣做:

char* name = "Mr. Anderson";

這實際上與此基本相同:

char name[] = "Mr. Anderson";

字符指針派上用場的地方是動態記憶。 您可以通過執行以下操作,隨時在程序中為char指針指定任意長度的字符串:

char *name;
name = malloc(256*sizeof(char));
strcpy(name, "This is less than 256 characters, so this is fine.");

或者,您可以使用strdup()函數為其分配,如下所示:

char *name;
name = strdup("This can be as long or short as I want.  The function will allocate enough space for the string and assign return a pointer to it.  Which then gets assigned to name");

如果以這種方式使用字符指針 - 並為其分配內存,則必須在重新分配之前釋放名稱中包含的內存。 像這樣:

if(name)
    free(name);
name = 0;

在嘗試釋放內存之前,請確保檢查該名稱實際上是一個有效點。 這就是if語句的作用。

您看到字符指針在C中被大量使用的原因是因為它們允許您使用不同大小的字符串重新分配字符串。 靜態字符數組不會這樣做。 他們也更容易傳球。

此外,字符指針很方便,因為它們可用於指向不同的靜態分配字符數組。 像這樣:

char *name;

char joe[] = "joe";
char bob[] = "bob";

name = joe;

printf("%s", name);

name = bob;
printf("%s", name);

這是將靜態分配的數組傳遞給帶有字符指針的函數時經常發生的情況。 例如:

void strcpy(char *str1, char *str2);

如果你然后傳遞:

char buffer[256];
strcpy(buffer, "This is a string, less than 256 characters.");

它將通過str1和str2操縱這兩者,它們只是指向緩沖區和字符串文字存儲在內存中的指針。

在函數中工作時要記住的事情。 如果您有一個返回字符指針的函數,請不要返回指向函數中分配的靜態字符數組的指針。 它將超出范圍,你會遇到問題。 重復一遍,不要這樣做:

char *myFunc() {
    char myBuf[64];
    strcpy(myBuf, "hi");
    return myBuf;
}

那不行。 在這種情況下,您必須使用指針並分配內存(如前所示)。 分配的內存將保持不變,即使您傳出函數范圍也是如此。 只是不要忘記如前所述釋放它。

這最終比我想要的更加百科全書,希望它有用。

編輯刪除C ++代碼。 我經常把兩者混在一起,我有時會忘記。

char * name只是一個指針。 沿線存儲器的某處必須分配存儲器名稱的存儲器地址。

  • 它可以指向單個字節的內存,並且是指向單個字符的“真實”指針。
  • 它可以指向一個連續的內存區域,它包含許多字符。
  • 如果這些字符恰好以null終結符結束,那么你可以看到一個指向字符串的指針。

在C中,字符串實際上只是一個字符數組,您可以從定義中看到。 然而,從表面上看,任何數組都只是指向其第一個元素的指針,請參閱下面的細微復雜性。 在C中沒有范圍檢查,您在變量聲明中提供的范圍僅對變量的內存分配有意義。

a[x]*(a + x) ,即指針a的解引用增加x。

如果您使用以下內容:

char foo[] = "foobar";
char bar = *foo;

欄將設為'f'

為了避免混淆並避免誤導人們,在指針和數組之間更復雜的差異上有一些額外的話,感謝avakar:

在某些情況下,指針實際上在語義上與數組不同,這是一個(非詳盡的)示例列表:

//sizeof
sizeof(char*) != sizeof(char[10])

//lvalues
char foo[] = "foobar";
char bar[] = "baz";
char* p;
foo = bar; // compile error, array is not an lvalue
p = bar; //just fine p now points to the array contents of bar

// multidimensional arrays
int baz[2][2];
int* q = baz; //compile error, multidimensional arrays can not decay into pointer
int* r = baz[0]; //just fine, r now points to the first element of the first "row" of baz
int x = baz[1][1];
int y = r[1][1]; //compile error, don't know dimensions of array, so subscripting is not possible
int z = r[1]: //just fine, z now holds the second element of the first "row" of baz

最后是一段有趣的瑣事; 因為a[x]等價於*(a + x)你實際上可以使用例如'3 [a]'來訪問數組a的第四個元素。 即以下是完全合法的代碼,並將'b'打印為字符串foo的第四個字符。

#include <stdio.h>

int main(int argc, char** argv) {
  char foo[] = "foobar";

  printf("%c\n", 3[foo]);

  return 0;
}

char *name ,就其本身而言, 不能包含任何字符 這個很重要。

char *name只聲明name是一個指針(即一個值為地址的變量),它將用於在程序后面的某個時刻存儲一個或多個字符的地址。 但是,它不會在內存中分配任何空間來實際保存這些字符,也不保證name甚至包含有效地址。 同樣,如果你有一個類似int number的聲明,那么在你明確設置它之前,無法知道number的值是什么。

就像聲明一個整數的值一樣,稍后你可以設置它的值( number = 42 ),在聲明一個指向char的指針之后,你可能稍后將其值設置為包含一個字符的有效內存地址 - 或者序列人物 - 你感興趣的。

這確實令人困惑。 理解和區分的重要事情是char name[]聲明數組和char* name聲明指針。 這兩個是不同的動物。

但是,C中的數組可以隱式轉換為指向其第一個元素的指針。 這使您能夠執行指針運算並遍歷數組元素(無論是什么類型的元素,無論是否為char )。 正如@which所提到的,您可以使用索引運算符或指針算法來訪問數組元素。 實際上,索引運算符只是指針運算的一種語法糖(同一表達式的另一種表示)。

將數組和指針之間的差異區分為數組的第一個元素非常重要。 可以使用sizeof運算符查詢聲明為char name[15]的數組的sizeof

char name[15] = { 0 };
size_t s = sizeof(name);
assert(s == 15);

但是如果你將sizeof應用於char* name你將獲得平台上指針的大小(即4個字節):

char* name = 0;
size_t s = sizeof(name);
assert(s == 4); // assuming pointer is 4-bytes long on your compiler/machine

此外,char元素數組的兩種形式的定義是等效的:

char letters1[5] = { 'a', 'b', 'c', 'd', '\0' };
char letters2[5] = "abcd"; /* 5th element implicitly gets value of 0 */

數組的雙重性質,數組到指向其第一個元素的指針的隱式轉換,在C(以及C ++)語言中,指針可以用作遍歷數組元素的迭代器:

/ *skip to 'd' letter */
char* it = letters1;
for (int i = 0; i < 3; i++)
    it++;

一個是實際的數組對象,另一個是指向這種數組對象的引用指針

可能令人困惑的是,兩者都有第一個字符的地址,但只是因為一個地址第一個字符而另一個地址是內存中包含字符地址的字。

可以在&name的值中看到差異。 在前兩種情況下,它與name只是相同的值,但在第三種情況下,它是一個不同的類型,稱為指向char的指針 ,或**char ,它是指針本身的地址。 也就是說,它是一個雙間接指針。

#include <stdio.h>

char name1[] = "fortran";
char *name2 = "fortran";

int main(void) {
    printf("%lx\n%lx %s\n", (long)name1, (long)&name1, name1);
    printf("%lx\n%lx %s\n", (long)name2, (long)&name2, name2);
    return 0;
}
Ross-Harveys-MacBook-Pro:so ross$ ./a.out
100001068
100001068 fortran
100000f58
100001070 fortran

暫無
暫無

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

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