C語言 (2)指針詳解

C語言指針詳解

爲什麼需要指針?

指針解決了一些編程中基本的問題。

第一,指針的使用使得不同區域的代碼可以輕易的共享內存數據。當然你也可以通過數據的複製達到相同的效果,但是這樣往往效率不太好,因爲諸如結構體等大型數據,佔用的字節數多,複製很消耗性能。但使用指針就可以很好的避免這個問題,因爲任何類型的指針佔用的字節數都是一樣的(根據平臺不同,有4字節或者8字節或者其他可能)。

 

第二,指針使得一些複雜的鏈接性的數據結構的構建成爲可能,比如鏈表,鏈式二叉樹等等。

 

第三,有些操作必須使用指針。如操作申請的堆內存。

 

還有一點:C語言中的一切函數調用中,實參傳遞給形參的機理都是“按值傳遞(pass by value)”的,如果我們要在函數中修改被傳遞過來的對象,就必須通過這個對象的指針來完成。

指針是什麼?

我們指知道:C語言中的數組是指 一類 類型,數組具體區分爲  int 類型數組,double類型數組,char數組 等等。同樣指針 這個概念也泛指 一類 數據類型,int指針類型,double指針類型,char指針類型等等。

通常,我們用int類型保存一些整型的數據,如 int num = 97 , 我們也會用char來存儲字符: char ch = 'a'。

我們也必須知道:任何程序數據載入內存後,在內存都有他們的地址,這就是指針。而爲了保存一個數據在內存中的地址,我們就需要指針變量。

因此:指針是程序數據在內存中的地址,而指針變量是用來保存這些地址的變量。

 

 

爲什麼程序中的數據會有自己的地址?

弄清這個問題我們需要從操作系統的角度去認知內存。

電腦維修師傅眼中的內存是這樣的:內存在物理上是由一組DRAM芯片組成的。

 

而作爲一個程序員,我們不需要了解內存的物理結構,操作系統將DRAM等硬件和軟件結合起來,給程序員提供的一種對物理內存使用的抽象。這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操作物理存儲器。所有的虛擬地址形成的集合就是虛擬地址空間。 

 

 

在程序員眼中的內存應該是下面這樣的。(假設使用的是32位系統平臺,虛擬存儲空間爲4GB)

 

也就是說,虛擬存儲器是一個很大的,線性的字節數組(平坦尋址)。每一個字節都是固定的大小,由8個二進制位組成。最關鍵的是,每一個字節都有一個唯一的編號,編號從0開始,一直到最後一個字節。如上圖中,這是一個4GB的虛擬存儲器的模型,它一共有4x1024x1024x1024 個字節,那麼它的虛擬地址範圍就是 0 ~ 4x1024x1024x1024-1 。

由於內存中的每一個字節都有一個唯一的編號,因此,在程序中使用的變量,常量,甚至數函數等數據,當他們被載入到內存中後,都有自己唯一的一個編號,這個編號就是這個數據的地址。指針就是這樣形成的。

下面用代碼說明

複製代碼

#include <stdio.h>

int main(void)
{
    char ch = 'a';
    int  num = 97;
    printf("ch 的地址:%p\n",&ch);   //ch 的地址:0028FF47
    printf("num的地址:%p\n",&num);  //num的地址:0028FF40
    return 0;
}

複製代碼

指針的值實質是內存單元(即字節)的編號,所以指針 單獨從數值上看,也是整數,他們一般用16進製表示。指針的值(虛擬地址值)使用一個機器字的大小來存儲,也就是說,對於一個機器字爲w位的電腦而言,它的虛擬地址空間是0~2w - 1 ,程序最多能訪問2w個字節。這就是爲什麼xp這種32位系統最大支持4GB內存的原因了。

我們可以大致畫出變量ch和num在內存模型中的存儲。(假設 char佔1個字節,int佔4字節)

 

 

操作系統爲什麼提供虛擬地址空間給程序員用而不是讓程序員直接使用物理地址空間?

本節內容屬於編程思想上的內容,可以先不看,或僅作了解。

1、提高物理內存的利用效率。

你可能會困惑,這怎麼就提高物理內存使用效率了呢?我這裏舉個共享單車的例子:假如一個國家有10個人,而只生產了2輛自行車(國家小,資源有限嘛~),這2輛車被2個人買了。把你自己想象爲那8個沒有自行車的人之一,你的思維是什麼——"我沒有自行車,我只能步行外出"。倘若是那2個有車的人呢——"我每次外出都可以騎車去,但是大部分時間,我的車是空閒的,沒其他人用"。後來,這個國家回收了這僅有的2輛車,把車刷成了統一的顏色,貼上二維碼,引入了共享單車系統,然後發出公告:只要車停在路邊沒人用,任何人都可以刷開騎走。那現在這10個人怎麼想呢——“只要我看到有空閒的單車,我就可以使用它”。

 
2、抽象的東西更加簡單穩定。

從古至今,我們對於“去飯館吃飯”這個抽象社會行爲沒有太大的變化——進入飯館,點菜,付錢,享用,走人。但是人們烹飪的方法卻發生了具大的改變,烹飪的器材、食材、食譜一直都在更新改進,如果你經常烹飪,你就需要不斷的學習,因爲你需要掌握做一道菜的每個細節。

回到內存相關的話題來:無論機器的內存用的是ddr3還是ddr4,是4G物理內存還是8G物理內存,程序員都似乎無需太過關心,因爲他們在編程時面向的是虛擬內存,而虛擬內存的模型到目前爲止都是固定的。這給程序員帶來非常大的便利,他們無需爲快速更新的計算機設備而改變自己的編程思維。

這並不意味着抽象的東西就一定不會改變。例如從“到飯館吃飯”到“點外賣”;從32位操作系統到64位操作系統。都屬於抽象的更新換代。


總結(我個人認爲):

  • 資產有限的情況下,使用合理的資產使用管理機制,可以使有限的資產服務於更多的人。
  • 抽象的事物更加簡單穩定,特定的事物更加複雜易變。
  • 底層通過給上層提供抽象服務來獲得利益,上層通過使用底層的抽象來獲得便利。

 變量和內存

 爲了簡單起見,這裏就用上面例子中的  int num = 97 這個局部變量來分析變量在內存中的存儲模型。

 

已知:num的類型是int,佔用了4個字節的內存空間,其值是97,地址是0028FF40。我們從以下幾個方面去分析。

 

1、內存的數據

     內存的數據就是變量的值對應的二進制,一切都是二進制。97的二進制是 : 00000000 00000000 00000000 0110000 , 但使用的小端模式存儲時,低位數據存放在低地址,所以圖中畫的時候是倒過來的。

 

2、內存數據的類型

     內存的數據類型決定了這個數據佔用的字節數,以及計算機將如何解釋這些字節。num的類型是int,因此將被解釋爲 一個整數。相同的內存數據以不同的數據類型去解析的時候,會得到不同的值,所以數據的類型是非常重要的。

 

3、內存數據的名稱

     內存的名稱就是變量名。實質上,內存數據都是以地址來標識的,根本沒有內存的名稱這個說法,這只是高級語言提供的抽象機制 ,方便我們操作內存數據。而且在C語言中,並不是所有的內存數據都有名稱,例如使用malloc申請的堆內存就沒有。

 

4、內存數據的地址

     如果一個類型佔用的字節數大於1,則其變量的地址就是其佔用的所有字節的地址值最小的那個字節的地址。因此num的地址是 0028FF40。 內存的地址用於標識這個內存塊。

 

5、內存數據的生命週期

     num是main函數中的局部變量,因此當main函數被啓動時,它被分配於棧內存上,當main執行結束時,消亡。    

如果一個數據一直佔用着他的內存,那麼我們就說他是“活着的”,如果他佔用的內存被回收了,則這個數據就“消亡了”。C語言中的程序數據會按照他們定義的位置,數據的種類,修飾的關鍵字等因素,決定他們的生命週期特性。實質上我們程序使用的內存會被邏輯上劃分爲:棧區,堆區,靜態數據區,代碼區。不同的區域的數據有不同的生命週期和讀寫權限。

     無論以後計算機硬件如何發展,內存容量都是有限的,因此清楚理解程序中每一個程序數據的生命週期是非常重要的。

     我會在以後的文章中再對C語言的內存管理做出介紹,敬請期待。

     

指針變量 和 指向關係

用來保存 指針(地址) 的變量,就是指針變量。如果指針變量p1保存了變量 num的地址,則就說:p1指向了變量num,也可以說p1指向了num所在的內存塊 ,這種指向關係,在圖中一般用 箭頭表示。

 

 

上圖中,指針變量p1指向了num所在的內存塊 ,即從地址0028FF40開始的4個byte 的內存塊。

 

這裏學2個名詞,讀英文資料的時候可能會用到

  • pointer:指針,例如上面例子中的p1
  • pointee:被指向的數據對象,例如上面例子中的num
  • 所以我們可以說:a pointer stores the address of a pointee

定義指針變量

 

C語言中,定義變量時,在變量名 前 寫一個 * 星號,這個變量就變成了對應變量類型的指針變量。必要時要加( ) 來避免優先級的問題。

 

引申:C語言中,定義變量時,在定義的最前面寫上typedef ,那麼這個變量名就成了一種類型,即這個類型的同義詞。

複製代碼

int a ; //int類型變量 a
int* p ; //int* 變量p
int arr[3]; //arr是包含3個int元素的數組
int (* parr )[3]; //parr是一個指向【包含3個int元素的數組】的指針變量


//-----------------各種類型的指針------------------------------

int* p_int; //指向int類型變量的指針 

double* p_double; //指向double類型變量的指針 

struct Student *p_struct; //結構體類型的指針

int(*p_func)(int,int); //指向返回類型爲int,有2個int形參的函數的指針 

int(*p_arr)[3]; //指向含有3個int元素的數組的指針 

int** p_pointer; //指向 一個整形變量指針的指針

複製代碼

 

取地址

既然有了指針變量,那就得讓他保存其它變量的地址,使用& 運算符取得一個變量的地址。

複製代碼

int add(int a , int b)
{
    return a + b;
}

int main(void)
{
    int num = 97;
    float score = 10.00F;
    int arr[3] = {1,2,3};

    //-----------------------

    int* p_num = &num;
    float* p_score = &score;
    int (*p_arr)[3] = &arr;           
    int (*fp_add)(int ,int )  = &add;  //p_add是指向函數add的函數指針
    return 0;
}

複製代碼

 

特殊的情況,他們並不一定需要使用&取地址:

  • 數組名的值就是這個數組的第一個元素的地址。
  • 函數名的值就是這個函數的地址。
  • 字符串字面值常量作爲右值時,就是這個字符串對應的字符數組的名稱,也就是這個字符串在內存中的地址。 

複製代碼

int add(int a , int b){
    return a + b;
}
int main(void)
{
    int arr[3] = {1,2,3};
    //-----------------------
    int* p_first = arr;
    int (*fp_add)(int ,int )  =  add;
    const char* msg = "Hello world";
    return 0;
}

複製代碼

 

解地址

我們需要一個數據的指針變量幹什麼?當然使用通過它來操作(讀/寫)它指向的數據啦。對一個指針解地址,就可以取到這個內存數據,解地址 的寫法,就是在指針的前面加一個*號。

解指針的實質是:從指針指向的內存塊中取出這個內存數據。

複製代碼

int main(void)
{
    int age = 19;
    int*p_age = &age;
    *p_age  = 20;  //通過指針修改指向的內存數據

    printf("age = %d\n",*p_age);   //通過指針讀取指向的內存數據
    printf("age = %d\n",age);

    return 0;
}

複製代碼

 

 

指針之間的賦值

指針賦值和int變量賦值一樣,就是將地址的值拷貝給另外一個。指針之間的賦值是一種淺拷貝,是在多個編程單元之間共享內存數據的高效的方法。

int* p1  = & num;
int* p3 = p1;

//通過指針 p1 、 p3 都可以對內存數據 num 進行讀寫,如果2個函數分別使用了p1 和p3,那麼這2個函數就共享了數據num。

 

空指針

指向空,或者說不指向任何東西。在C語言中,我們讓指針變量賦值爲NULL表示一個空指針,而C語言中,NULL實質是 ((void*)0) ,  在C++中,NULL實質是0。

換種說法:任何程序數據都不會存儲在地址爲0的內存塊中,它是被操作系統預留的內存塊。

 

下面代碼摘自 stddef.h

#ifdef __cplusplus
     #define NULL    0
#else    
     #define NULL    ((void *)0)
#endif

 

壞指針

 

指針變量的值是NULL,或者未知的地址值,或者是當前應用程序不可訪問的地址值,這樣的指針就是壞指針,不能對他們做解指針操作,否則程序會出現運行時錯誤,導致程序意外終止。

任何一個指針變量在做 解地址操作前,都必須保證它指向的是有效的,可用的內存塊,否則就會出錯。壞指針是造成C語言Bug的最頻繁的原因之一。

 

下面的代碼就是錯誤的示例。

複製代碼

void opp()
{
     int*p = NULL;
     *p = 10;      //Oops! 不能對NULL解地址
}

void foo()
{
     int*p;
     *p = 10;      //Oops! 不能對一個未知的地址解地址
}

void bar()
{
     int*p = (int*)1000; 
     *p =10;      //Oops!   不能對一個可能不屬於本程序的內存的地址的指針解地址
}

複製代碼

 

指針的2個重要屬性

指針也是一種數據,指針變量也是一種變量,因此指針 這種數據也符合前面 變量和內存 主題中的特性。 這裏我只想強調2個屬性: 指針的類型,指針的值。

複製代碼

int main(void)
{
    int num = 97;
    int *p1  = &num;
    char* p2 = (char*)(&num);

    printf("%d\n",*p1);    //輸出  97
    putchar(*p2);          //輸出  a
    return 0;
}

複製代碼

 

指針的值:很好理解,如上面的num 變量 ,其地址的值就是0028FF40 ,因此 p1的值就是0028FF40。數據的地址用於在內存中定位和標識這個數據,因爲任何2個內存不重疊的不同數據的地址都是不同的。

指針的類型:指針的類型決定了這個指針指向的內存的字節數並如何解釋這些字節信息。一般指針變量的類型要和它指向的數據的類型匹配。

 

 

由於num的地址是0028FF40,因此p1  和  p2的值都是0028FF40

*p1  :  將從地址0028FF40 開始解析,因爲p1是int類型指針,int佔4字節,因此向後連續取4個字節,並將這4個字節的二進制數據解析爲一個整數 97。

*p2  :  將從地址0028FF40 開始解析,因爲p2是char類型指針,char佔1字節,因此向後連續取1個字節,並將這1個字節的二進制數據解析爲一個字符,即'a'。

 

同樣的地址,因爲指針的類型不同,對它指向的內存的解釋就不同,得到的就是不同的數據。

 

 

void*類型指針 

由於void是空類型,因此void*類型的指針只保存了指針的值,而丟失了類型信息,我們不知道他指向的數據是什麼類型的,只知道這個數據在內存中的起始地址,如果想要完整的提取指向的數據,程序員就必須對這個指針做出正確的類型轉換,然後再解指針。前面已經提到過,數據的類型是正確解析出內存數據的關鍵,相同的內存數據以不同的數據類型去解析的時候,會得到不同的值。程序需要得到什麼數據,不光要知道其地址,還要明確其類型,因此編譯器不允許直接對void*類型的指針做解指針操作。

 

結構體和指針

結構體指針有特殊的語法:  -> 符號 

如果p是一個結構體指針,則可以使用 p ->【成員】 的方法訪問結構體的成員。p->member 等價於 (*p).member。

複製代碼

typedef struct
{
    char name[31];
    int age;
    float score;
}Student;

int main(void)
{
    Student stu = {"Bob" , 19, 98.0};
    Student*ps = &stu;

    ps->age = 20;
    ps->score = 99.0;
    printf("name:%s age:%d\n",ps->name,ps->age);
    return 0;
}

複製代碼

 

數組和指針

 

1、數組名作爲右值的時候,就是第一個元素的地址。

複製代碼

int main(void)
{
    int arr[3] = {1,2,3};
 
    int*p_first = arr;
    printf("%d\n",*p_first);  //1
    return 0;
}

複製代碼

 

2、指向數組元素的指針 支持 遞增 遞減 運算。(實質上所有指針都支持遞增遞減 運算 ,但只有在數組中使用纔是有意義的)

 

複製代碼

int main(void)
{
    int arr[3] = {1,2,3};
 
    int*p = arr;
    for(;p!=arr+3;p++){
        printf("%d\n",*p); 
    }
    return 0;
}

複製代碼

3、p= p+1 意思是,讓p指向原來指向的內存塊的下一個相鄰的相同類型的內存塊。

      同一個數組中,元素的指針之間可以做減法運算,此時,指針之差等於下標之差。

 

4、p[n]    == *(p+n)

     p[n][m]  == *(  *(p+n)+ m )

 

5、當對數組名使用sizeof時,返回的是整個數組佔用的內存字節數。當把數組名賦值給一個指針後,再對指針使用sizeof運算符,返回的是指針的大小。

 

這就是爲什麼我麼將一個數組傳遞給一個函數時,需要另外用一個參數傳遞數組元素個數的原因了。

複製代碼

int main(void)
{
    int arr[3] = {1,2,3};
 
    int*p = arr;
    printf("sizeof(arr)=%d\n",sizeof(arr));  //sizeof(arr)=12
    printf("sizeof(p)=%d\n",sizeof(p));   //sizeof(p)=4
 
    return 0;
}

複製代碼

 

 函數和指針

函數的參數和指針

C語言中,實參傳遞給形參,是按值傳遞的,也就是說,函數中的形參是實參的拷貝份,形參和實參只是在值上面一樣,而不是同一個內存數據對象。這就意味着:這種數據傳遞是單向的,即從調用者傳遞給被調函數,而被調函數無法修改傳遞的參數達到回傳的效果。

複製代碼

void change(int a)
{
    a++;      //在函數中改變的只是這個函數的局部變量a,而隨着函數執行結束,a被銷燬。age還是原來的age,紋絲不動。
}
int main(void)
{
    int age = 19;
    change(age);
    printf("age = %d\n",age);   // age = 19
    return 0;
}

複製代碼

 

有時候我們可以使用函數的返回值來回傳數據,在簡單的情況下是可以的,但是如果返回值有其它用途(例如返回函數的執行狀態量),或者要回傳的數據不止一個,返回值就解決不了了。

 

傳遞變量的指針可以輕鬆解決上述問題。

複製代碼

void change(int* pa)
{
    (*pa)++;   //因爲傳遞的是age的地址,因此pa指向內存數據age。當在函數中對指針pa解地址時,
               //會直接去內存中找到age這個數據,然後把它增1。
}
int main(void)
{
    int age = 19;
    change(&age);
    printf("age = %d\n",age);   // age = 20
    return 0;
}

複製代碼

 

再來一個老生常談的,用函數交換2個變量的值的例子:

複製代碼

#include<stdio.h>
void swap_bad(int a,int b);
void swap_ok(int*pa,int*pb);

int main()
{
    int a = 5;
    int b = 3;
    swap_bad(a,b);       //Can`t swap;
    swap_ok(&a,&b);      //OK
    return 0;
}

//錯誤的寫法
void swap_bad(int a,int b)
{
    int t;
    t=a;
    a=b;
    b=t;
}

//正確的寫法:通過指針
void swap_ok(int*pa,int*pb)
{
    int t;
    t=*pa;
    *pa=*pb;
    *pb=t;
}

複製代碼

 

 

有的時候,我們通過指針傳遞數據給函數不是爲了在函數中改變他指向的對象,相反,我們防止這個目標數據被改變。傳遞指針只是爲了避免拷貝大型數據。

考慮一個結構體類型Student。我們通過show函數輸出Student變量的數據。

 

複製代碼

typedef struct
{
    char name[31];
    int age;
    float score;
}Student;


//打印Student變量信息
void show(const Student * ps)
{
    printf("name:%s , age:%d , score:%.2f\n",ps->name,ps->age,ps->score);   
}

copycode.gifuploading.4e448015.gif轉存失敗重新上傳取消複製代碼

 

我們只是在show函數中取讀Student變量的信息,而不會去修改它,爲了防止意外修改,我們使用了常量指針去約束。另外我們爲什麼要使用指針而不是直接傳遞Student變量呢?

從定義的結構看出,Student變量的大小至少是39個字節,那麼通過函數直接傳遞變量,實參賦值數據給形參需要拷貝至少39個字節的數據,極不高效。而傳遞變量的指針卻快很多,因爲在同一個平臺下,無論什麼類型的指針大小都是固定的:X86指針4字節,X64指針8字節,遠遠比一個Student結構體變量小。

 

函數的指針

 

每一個函數本身也是一種程序數據,一個函數包含了多條執行語句,它被編譯後,實質上是多條機器指令的合集。在程序載入到內存後,函數的機器指令存放在一個特定的邏輯區域:代碼區。既然是存放在內存中,那麼函數也是有自己的指針的。

C語言中,函數名作爲右值時,就是這個函數的指針。

 

複製代碼

void echo(const char *msg)
{
    printf("%s",msg);
}
int main(void)
{
    void(*p)(const char*) = echo;   //函數指針變量指向echo這個函數

    p("Hello ");      //通過函數的指針p調用函數,等價於echo("Hello ")
    echo("World\n");
    return 0;
}

複製代碼

 

const 和 指針

const到底修飾誰?誰纔是不變的?

 

下面是我總結的經驗,分享一下。

如果const 後面是一個類型,則跳過最近的原子類型,修飾後面的數據。(原子類型是不可再分割的類型,如int, short , char,以及typedef包裝後的類型)

如果const後面就是一個數據,則直接修飾這個數據。

複製代碼

int main()
{
    int a = 1;
   
    int const *p1 = &a;        //const後面是*p1,實質是數據a,則修飾*p1,通過p1不能修改a的值
    const int*p2 =  &a;        //const後面是int類型,則跳過int ,修飾*p2, 效果同上

    int* const p3 = NULL;      //const後面是數據p3。也就是指針p3本身是const .

    const int* const p4 = &a;  // 通過p4不能改變a 的值,同時p4本身也是 const
    int const* const p5 = &a;  //效果同上

    return 0;

}

複製代碼

 

複製代碼

typedef int* pint_t;  //將 int* 類型 包裝爲 pint_t,則pint_t 現在是一個完整的原子類型

int main()
{
   
    int a  = 1;
    const pint_t p1 = &a;  //同樣,const跳過類型pint_t,修飾p1,指針p1本身是const
    pint_t const p2 = &a;  //const 直接修飾p,同上
   
    return 0;

}

複製代碼

 

深拷貝和淺拷貝

如果2個程序單元(例如2個函數)是通過拷貝 他們所共享的數據的 指針來工作的,這就是淺拷貝,因爲真正要訪問的數據並沒有被拷貝。如果被訪問的數據被拷貝了,在每個單元中都有自己的一份,對目標數據的操作相互 不受影響,則叫做深拷貝。

 

 

 

附加知識

指針和引用這個2個名詞的區別。他們本質上來說是同樣的東西。指針常用在C語言中,而引用,則用於諸如Java,C#等 在語言層面封裝了對指針的直接操作的編程語言中。引用是編程語言提供給程序員的抽象機制,而指針是操作系統提供給軟件開發模型的抽象機制。

大端模式和小端模式

 

1) Little-Endian就是數據的低字節排放在內存的低地址端,高字節排放在內存的高地址端。個人PC常用,Intel X86處理器是小端模式。

2) B i g-Endian就是數據的高字節排放在內存的低地址端,低字節排放在內存的高地址端。

 

採用大端方式 進行數據存放符合人類的正常思維,而採用小端方式進行數據存放利於計算機處理。有些機器同時支持大端和小端模式。

 

假如 short類型佔用2個字節,且存儲的地址爲0x30。

short a = 1;

 

如下圖:

 

  

複製代碼

#include<stdio.h>

//測試機器使用的是否爲小端模式。是,則返回true,否則返回false
//這個方法判別的依據就是:C語言中一個對象的地址就是這個對象佔用的字節中,地址值最小的那個字節的地址。
int isSmallIndain(void)
{
      unsigned short val = 0x0001;
      unsigned char* p = (unsigned char*)&val;  //C/C++:對於多字節數據,取地址是取的數據對象的第一個字節的地址,也就是數據的低地址
      
      return (*p == 0x01);
}
 
int main(void)
{

    if(isSmallIndain())
    {
        puts("小端"); 
    } else{
        puts("大端"); 
    }

    return 0;

}

複製代碼

 

第二種方法,使用union類型

複製代碼

#include<stdio.h>


typedef union {
    unsigned short us;
    unsigned char  uc;
}Test_t;
 
int main(void)
{
    Test_t val;
    val.us = 0x0001;
    
    if(val.uc==0x01)
    {
        puts("小端"); 
    } else{
        puts("大端"); 
    }

    return 0;

}

複製代碼

 

複製代碼

#include<stdio.h>


//打印出一個unsigned short int 類型的原始字節流 
//這個例子中很明顯看到,取到a的首地址後,我們循環遞增了p,而非遞減p,也從來不會看到有從首地址遞減輸出數據的字節的寫法。
//這也就佐證了:在C語言中,對於一個多字節數據,它的地址就是它佔用的所有字節中的地址值最小的那個字節的虛擬空間地址 
//這也又說明了一個事實:C語言中,一個多字節數據類型的實例,佔用的虛擬內存空間是連續的。 
int main(void)
{    
    size_t i;
    unsigned short int a = 0xA1FF;
    unsigned char*p = (unsigned char*)&a;
    
    for( i=0;i<sizeof(a);++i)
    {
        printf("%#x ",*p);   //小端平臺輸出:0xFF 0xA1
        p++;                 //大端平臺輸出:0xA1 0xFF  
         
    }
    printf("\n\n");

    return 0;
}

複製代碼

 

參考網址:https://www.cnblogs.com/lulipro/p/7460206.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章