首頁 > 軟體

C語言小知識之為什麼要使用指標詳析

2021-10-15 16:00:03

剛開始學習C語言的時候,感覺最難理解的就是指標,什麼指標變數,變數指標,指向指標的變數,指向變數的指標?一堆概念,搞得人云裡霧裡的,今天不討論這些概念的問題,從最底層來分析C語言中為什麼要使用指標,指標存在的意義又是什麼呢?

首先從一個簡單的例子來看,寫一段程式碼來交換x、y的值。

void main( void )
{
    u8 x = 10, y = 20;
    u8 temp;

    __asm( "sim" );                             //禁止中斷
    SysClkInit();
    delay_init( 16 );
    LED_GPIO_Init();
    Uart1_IO_Init();
    Uart1_Init( 9600 );
    ADC_GPIO_Init();
    __asm( "rim" );                             //開啟中斷
    while( 1 )
    {
        LED = ~LED;

        printf( "x = %d,y = %drn", x, y );

        temp = x;
        x = y;
        y = temp;

        printf( "---> x = %d,y = %d rnrnrn", x, y );

        delay_ms( 200 );
    }
}

在STM8微控制器中,交換x、y的值,並將值列印出來,列印結果如下:

通過第三個變數temp很輕鬆的就將x、y的值交換了。

但是為了程式的美觀性,不想再主程式中寫這麼多的程式碼,於是決定用一個函數在外部來實現這個功能。

void swap( u8 x, u8 y )
{
    u8 temp;
    temp = x;
    x = y;
    y = temp;
}

void main( void )
{
    u8 x = 10, y = 20;
    __asm( "sim" );                             //禁止中斷
    SysClkInit();
    delay_init( 16 );
    LED_GPIO_Init();
    Uart1_IO_Init();
    Uart1_Init( 9600 );
    ADC_GPIO_Init();
    __asm( "rim" );                             //開啟中斷
    while( 1 )
    {
        LED = ~LED;

        printf( "x = %d,y = %drn", x, y );
        sawp( x, y );
        printf( "---> x = %d,y = %d rnrnrn", x, y );

        delay_ms( 200 );
    }
}

在主函數外面定義一個函數,專門用來交換x,y的值。程式執行結果如下:

此時發現x和y的值並沒有交換,這是怎麼回事?那麼在交換函數內部,也將x和y的值列印出來。修改程式碼如下:

void swap( u8 x, u8 y )
{
    u8 temp;
    
    printf( "in: x = %d,y = %drn", x, y );
    temp = x;
    x = y;
    y = temp;
    
    printf( "in: ---> x = %d,y = %d rnrnrn", x, y );
}

列印結果如下:

可以看到在交換函數內部,x和y的值已經交換了,但是在函數外部,x和y的值並沒有交換。這是為什麼呢?單步偵錯直接觀察x和y在微控制器內部分儲存情況。

進入主程式之後,首先觀察主函數內的x和y在記憶體中的儲存情況,x值為10,也就是16進位制的0x0A,在記憶體中0x000009的位置儲存,y的值為20,也就是16進位制的0x14,在記憶體中0x00000B,位置儲存。

接下里進入到swap函數中。為了方便觀察,將swap函數內部的x和y替換成了m和n。

可以看出在進入子函數之後,m和n的地址和值,都和main函數中的x和y一樣。接下來交換m和n的值。

交換完成後發現,記憶體中0x000009位置的值和0x00000B位置的值亞發生了交換。然後退出子函數,返回到main函數中。

這時候奇怪的事情發生了,剛才記憶體中交換的值又變回來了?這是為什麼呢?在這裡就不得不說在C語言中關於區域性變數的問題,區域性變數在C語言中是沒有固定的儲存位置的,它是由系統的堆疊統一來管理的,當進入函數內部時,系統就會給這些區域性變數臨時分配一個儲存空間儲存它的值,當程式要離開函數時,系統就會將區域性變數的值儲存在堆疊中,然後變數的儲存位置就被釋放了。當程式進入另一個函數中時,又會給這個函數內部區域性變數分配空間,這時候可能就會出現兩個函數中的區域性變數都使用了同一個記憶體空間。此時這個記憶體空間的值改變後,並不影響上一個函數中區域性變數的值,因為上一個函數中的區域性變數值此時在堆疊中存放。當程式要離開當前的這個函數時,又會將當前的區域性變數值儲存在堆疊中。回到上一個函數後,又將堆疊中儲存的值恢復給變數,此時變數的地址又是臨時申請的,可能此刻申請的地址值還是和上一次一樣。但是並不代表這個地址就永遠屬於這個區域性變數。

這個就很類似於超市中的儲物櫃,你要進去超市買東西,先將東西存到一個櫃子中,買完東西后,又將東西儲存物櫃取了出來。然後隔了幾個小時,又要去這個超市買東西,又需要將東西存起來,但是此時儲存的櫃子編號還是上上一次儲存時一樣。但是這並不能代表這個櫃子的編號就是專屬於你的了。它只是儲物櫃臨時分配給你的空間,當你取出東西后這個空間就會被系統收回,如果你下一次還需要用,系統又會自動給你分配,但是這兩次分配的剛好是一個編號而已。

那麼要如何解決這種變數交換的問題呢?有兩種方法,第一種就是直接將要交換的這個兩個變數定義為全域性變數,讓它在程式執行的過程中獨佔一個地址空間,這樣就不會有其他變數來使用這個位置了。但是這樣的話就會比較浪費記憶體空間,只使用了一次,但是卻要永久的佔用。第二種方法就是直接使用指標。

下面將程式碼改成使用指標的方式。

void swap( u8 *m, u8 *n )
{
    u8 temp;   
    temp = *m;
    *m = *n;
    *n = temp;    
}
void main( void )
{
    u8 x = 10, y = 20;
    __asm( "sim" );                             //禁止中斷
    SysClkInit();
    delay_init( 16 );
    LED_GPIO_Init();
    Uart1_IO_Init();
    Uart1_Init( 9600 );
    ADC_GPIO_Init();
    __asm( "rim" );                             //開啟中斷
    while( 1 )
    {
        LED = ~LED;

        printf( "x = %d,y = %drn", x, y );
        sawp( &x, &y );
        printf( "---> x = %d,y = %d rnrnrn", x, y );

        delay_ms( 200 );
    }
}

在向swap函數傳遞引數的時候需要使用&符號。列印輸出結果

此時x和y的值已經成功交換了。現在也將子函數內部的交換情況列印一下。

void swap( u8 *m, u8 *n )
{
    u8 temp;
    
    printf( "in: m = %d,n = %drn", m, n );
    temp = *m;
    *m = *n;
    *n = temp;
    
    printf( "in: ---> m = %d,n = %d rnrnrn", m, n );
}

從輸出的結果來看,怎麼m和n的值一個時1021,一個時1020.這個值是什麼呢?直接單步偵錯看。

此時main函數中x的地址變成了0x0003FD,y的地址變成了0x0003FC.接著進入子函數。

這時可以看出m的值為0x03FD,n的值為0x03FC.*m的值為0x0A也是就10,*0x14也就是20.
接下來開始交換值。

可以看出m和n的值沒變,但是m和n的值交換了。接下來回到主函數中。

此時主函數中x和y的值也交換了。

那剛才串列埠列印出來的1021和1020是什麼呢?1021的十六進位制是0x03FD,1020的十六進位制是0x03FC.也就是說剛才列印的m的值和x的地址一樣,n的值和y的地址一樣。

那為什麼m和n會變成x和y地址,*m 和 *n又會變成 x 和 y 的值。這裡就要說指標的本質了。在記憶體內部,它是不認識什麼變數和指標的,對於儲存空間來說,它只有地址和值。也就是說在什麼地址處,儲存什麼值。對於普通的變數來說,變數的名稱就會被編譯器編譯成地址,也就是說x和y就是它自己地址的別名。x和y的值就是地址中對應的值。

當操作普通變數x和y的時候,系統預設操作的就是它的值。而指標剛好和它相反,指標預設是把地址作為它的值,當操作指標的時候,預設操作的就是地址。為了將普通變數和指標進行區分,那麼如果要使用指標的時候,就需要給它貼一個標籤,告訴系統,我這個是特殊變數,它是直接操作地址的,不是操作值的。

所以在定義指標的時候給變數前面加一個*號,就表示告訴系統,我這個是特殊的。比如定義了一個int *m。就表示告訴系統,當我預設操作m的時候,你就給我它的地址,而不要給我它的值。當需要取值的時候就需要新增上標籤 *m,告訴系統我現在要取的值,不是地址,這是特殊情況,不要把預設的地址給我。

當要將普通變數傳遞給指標時,因為直接操作變數預設就是普通變數的值,而指標存貯的是地址,所以當普通變數和指標傳遞資料的時候,也要給普通變數新增一個標籤 & ,這個符號就告訴系統,我現在不要預設的值,我要的是特殊情況的地址。

所以將x和y傳遞給指標的時候,前面要加&符號。

swap( &x, &y );對應的就是 swap( u8 *m, u8 *n );

剛才上面不是說了嗎,指標預設的是地址,加上*號就是值了。那麼這樣直接傳遞過去不就是相當於 *m = &x 了嗎?

由於這個子函數是定義和傳值在一起操作了,省略了一步,標準操作應該是。

int *m;

m=&x;

先定義一個指標,然後將普通變數的特殊情況,也就是取普通變數的地址,傳遞給指標的預設情況。這樣m的預設情況下就代表的值x的地址,而x的值就是*m。

如果定義變數和給變數賦值在一條語句時,上面的程式碼就可以簡寫為

int *m = &x;

所以上面的函數 swap( &x, &y ); 給指標傳遞值的時候,指標的定義和賦值是在一條語句完成的, swap( u8 *m, u8 *n ); 這是一種常用的簡寫形式。

在swap函數內部操作 *m 也就相當於直接操作的是x,操作 *n 就是直接操作的y,所以交換 *m 和 *n的值,就相當於交換x和y的值。

在沒有使用指標時通過子函數交換,此時傳遞的是變數的值,相當於把變數的值拷貝了一份,給了子程式。

而有了指標之後,相當於將變數的地址直接給了子函數。相當於給變數x和y又起了一個別名。操作別名的時候,也就相當於直接操作的是x。

由此可見,指標只是為了方便編寫程式而設定的一種給變數起別名的方法,也就是相當於給自己櫃子配了了一把鑰匙。只要別人有這個鑰匙,也就可以開啟你的櫃子。所以指標在使用的時候會有危險性。

如果系統中有關鍵資料,那麼如果這個資料用指標傳遞給了外部函數,那麼當外部函數修改資料的時候,系統就會存在風險。有可能外部函數修改了一個值,而這個值是非法的,自己的系統就奔潰了。所以在使用指標的時候一定要注意安全性問題。

通過上面的例子,相信對指標就有了更深層次的理解了。它是為了方便操作變數而設定的特殊情況,是被貼了標籤的變數。至於什麼指標變數,變數指標,那都是起的名字而已,搞不清楚這些概念也不用去糾結,只要在使用的時候,知道如何使用就行了。

總結

到此這篇關於C語言小知識之為什麼要使用指標的文章就介紹到這了,更多相關C語言使用指標內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com