標籤:

C 指針傳遞變數為什麼無法修改變數值?

題目要求就是給以下函數糾錯:

void GetMemory( char*p )
{
p = (char*) malloc( 100 );
}
void Test( void )
{
char*str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}

答案是這樣的: 傳入中GetMemory(char *p )函數的形參為字元串指針,在函數內部修改形參並不能真正的改變傳入形參的值,執行完後的str仍然為NULL;

可是我的理解是: 當指針作為函數的參數進行傳遞的時候,本質上還是進行的「值傳遞」,也就是複製了一個新的指向該地址的指針變數, 那麼我在函數中在堆上給該地址分配一塊內存, 難道函數外就沒有了嗎?

T_T 求指導, 我的理解有什麼問題..


我以前學習的時候也陷入到過這樣的誤區,所以我想分享一下我後面理解與解決的方式,希望題主也能理解到。我覺得有關指針的問題,其實最好的理解方式莫過於繪圖。

從Test函數執行開始,str指向的是NULL

然後經過GetMemory函數以後,在這裡發生了一次賦值,即 char *p = str;

所以,p指向了(str指向的NULL),於是就變為了如此

然而經過了GetMemory的malloc以後,p獲得了一塊新內存的地址,於是p的指向就變為了這樣

那麼,從這裡可以看出來str與p就分道揚鑣了,而str也還是指向NULL。

那麼,如何修改呢?一個是C++的引用,另外一個就是二重指針,即把GetMemory變為 GetMemory(char ** p) 來保存str的地址。

那麼,針對這兩種情況,樓主是否可以嘗試這樣的繪圖,然後來解釋為什麼這樣就可以做到了呢?(我當初也是在理解二重指針的時候遇到了這樣的問題,然後通過繪圖理解了為什麼可以,所以題主也可以試試)


題主,你可以這樣簡化一下,會更容易看懂(既然是C++,我就用C++了,你的題目是C):

int * p = nullptr; // 空指針,類似C里的NULL

int * q = p; // 此時p和q都是nullptr 這裡的q你可以把它視作GetMemory里的參數

q = new int; // q不再是nullptr,而p仍然是

也就是說,你一開始有倆毫不相干的空指針,然後你給其中一個分配了空間,另一個當然一直沒變過。


這代碼就是純粹的垃圾,硬要糾錯的話,糾完你也可以準備下家面試了。。。。

很多人硬要問我糾錯,那就更新一下。

======================

硬是要糾錯的話,至少有如下幾點

1,java 名字 sb

2,指針參數檢查

3,錯誤處理和返回和檢查

4,strcpy 函數是不安全的,現在已經不應該用了

5,free

6,格式化輸出應用字面量字元串

我就不說沒 main 函數這些了。。。

你說,我面試的時候如果這麼說,對方會不會覺得特沒面子,會不會覺得你這人怎麼這麼事兒呀,是不是可以直接準備下一家面試了呢

:hhh

&>&>&>&>&>&>&>那就只說對方想聽的標準答案吧,見下圖

  • str 是一個變數,它的地址是 1,地址 1 中存的數字是 0 (NULL)

  • p 是一個變數,它的地址是 3,地址 3 中存的數字是 0 (這一步是把地址 1 中的數字拷貝到地址 3)

  • malloc 返回一個數字,這個數字是新申請的內存的首地址,通過 = 將這個數字寫入了變數 p 也就是地址 3 的內存中

所有變數都是一個地址,它的值不過是地址中的那個數字

變數賦值就是複製數字

就說這麼多吧,反正只是掏糞的面試和掏糞的工作,畢竟這是 java 出身的碼畜打著 cpp 旗號寫的 c 語言代碼


你想要修改參數,就要傳參數的指針

同理,你想要修改指針參數,就要傳指針參數的指針

另外這tm是C

C++裡面我們一般不用指針,想修改參數我們一般用引用


C函數都是值傳遞,所謂的地址傳遞是靠傳遞指針的值來實現的。

void GetMemory( char**p )
{
*p = (char*) malloc( sizeof(char) * 100 );
}
void Test( void )
{
char*str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}


我在函數中在堆上給該地址分配一塊內存, 難道函數外就沒有了嗎?

Getmemory函數執行完畢後你就沒有辦法再訪問那塊內存,這就是內存泄漏。


函數調用可以當成內聯了一段代碼,只是最後有個返回值:

void GetMemory( char*p )
{
p = (char*) malloc( 100 );
}
void Test( void )
{
char*str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}

相當於:

void Test( void )
{
char*str = NULL;
{//引入一層scope
char* p=str;//初始化參數
p = (char*) malloc( 100 );
}
strcpy( str, "hello world" );
printf( str );
}

正好這裡沒有返回值就不去單獨考慮了——寫成這樣你能看懂了吧?

ps:還有就是printf(str)很危險,誰知道str裡面有沒有百分號


void GetMemory( char ** p )
{
 *p = (char*) malloc( 100 );
}
void Test( void )
{
 char*str = NULL;
 GetMemory( str );
 strcpy( str, "hello world" );
 printf( str );
}


應該傳入二級指針,當前修改的值仍是str指向的值,而在子函數中對他的操作是無效的


因為GetMemory裡面的p並不是傳引用,而是傳值。

看Windows API的常式就會發現,傳入一個指針變數時從來不會去試圖改變它的值,因為這個改變被調用者是不知道的。

傳進指針後你去對他malloc,執行完了這塊內存就是野內存了,這是更大的錯誤

建議再抽象一層,用一個結構體把指針裝裡面(Windows API下都是這樣做)。


void GetMemory( char*p )
{
p = (char*) malloc( 100 );
}

·

你從這個函數的形式也能看出來,它是不能為外部的那個傳入參數的src復值的。

因為函數體中寫的是 p=。。。 (在非引用的情況下,是外部參數的「值拷貝」)。

換句話說,你賦值的對象,是函數體的 stackframe 裡面的參數部分。

而不是 *p=。。。(這是典型為函數外部對象賦值的形式),也等價於 p[0] = ...;

當然如果你寫成引用就可以了:

void GetMemory((char*) p)

{

p=...;

}

等價於

void getMemory(char **pp)

{

*pp=(char*)mallo(...);

}

在教材上會使用實際參數,形式參數這樣的術語,但是我覺得不是很容易讓初學者分清楚這兩個術語具體指代什麼。。。

當然這裡還有一個給字元串賦值的函數,非常容易和上面的這種情況混淆,比如說:

void foo(char* p)

{

strcpy(p, "hello");

}

char s[16] = "123456";

foo(s);

這個函數可以修改字元串(的值),不能修改的,是傳入的那個指向該字元串的指針的指向。


運行起來大致是這樣的:

char* str = NULL:
此時變數str的值為0。

調用GetMemory(str):
把變數str的值複製到內層棧上的變數p,於是p的值是0。

p = (char*) malloc(xxx);
把malloc分配的首地址,比如0x123456,賦給p。此時p的值為0x123456。
並沒有變數str什麼事。

這裡的關鍵在於,你需要修改變數str。

當然,實際上還有一堆問題,比如:

  • 分配了固定尺寸的空間,卻使用了strcpy,可以溢出。

  • 把字元串直接塞到printf的第一個參數。如果字元串含有任何printf認為是格式的內容,都會崩掉。

至少應當改到這種程度:

const char* resource = "fuck";

/* 駝峰是異端 */
void get_memory(char** ptr, size_t size)
{
*ptr = (char*) malloc(sizeof(**ptr) * size);
}

void Test()
{
char* str = NULL;
get_memory(str, strlen(resource) + 1);
strcpy(str, resource);
printf("%s
", str);
}


你該這麼理解,傳入了指針,或者說是數組的首地址。

如果是數組的首地址,你就能修改這個數組的內容

但如果把它看作是指針,指針本質上可以理解為一個整型,那麼傳入一個整型就是值傳遞,拷貝了一次這個指針但沒法修改原來的它。

如果想要修改,很簡單,傳指針的地址,int**


想在函數內改變一個外部變數的值,你要傳它的指針進去。So, 你要改變一個指針變數的值,你得傳這個指針變數的指針進去,而不是傳這個指針變數進去……


首先指針也是變數,也有內存空間存儲它的值,當然它的值又是另外一個內存的地址。

參數的值傳遞時,是這樣的

然後你給指針p賦值,只會改變存儲p的那塊內存的值,就像這樣

所以str指向的地址還是NULL…

用引用或者多重指針就好啦,指針也是一個數據類型,所以也有它對應的指針類型。


首先這是一道c的面試題

void xxx(char** p)

{*p=malloc}

c++的話

void xxx(char* p)

{p=new}

另外malloc要free new要delete


所謂傳遞指針,終究也是copy指針再傳遞,所以到了GetMemory,p已經不再是那個p。


前面有人已經回答地很好了,只要記住,1.c只有傳值調用;2.指針也是一個變數,也有內存空間用於存儲它的值,就不難理解了


要傳遞指針的值,你需要指針的指針


指針和普通變數一個樣啊, 只不過指針存的是變數的地址而已, 所以可以有指針的指針的指針 int*** p; 除了這個, 指針與其他變數沒有兩樣.

我發現很多人糾結指針, 都是沒明白這一點.

不過題主糾結的不是這個, 題主對於指針的值傳遞理解是正確的, 但是, 對於重新分配內存的理解不對, 重新分配內存的過程是在堆上分配新的內存, 然後把新的內存的地址賦值給指針, 不是在該指針的地址重新分配內存.


推薦閱讀:

不學C語言,直接學C++會有問題嗎?
怎麼理解 C 語言是面向過程的語言,C++ 是面向對象的語言。?
gcc環境下不能使用gets怎麼辦?
怎麼理解C語言的複雜聲明?
C語言中的指針為什麼要區別出指向不同數據類型的指針?

TAG:C編程語言 |