首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言指針深度解析:類型、運算、數組、結構體、函數指針與安全陷阱全攻略

     編輯:關於C語言

C語言指針深度解析:類型、運算、數組、結構體、函數指針與安全陷阱全攻略

指針是C語言的靈魂,也是最大難點。本文從聲明解讀、四大核心屬性、指針運算、表達式、與數組/結構體/函數的交互、類型轉換、安全陷阱八個維度,系統構建指針知識體系,助你徹底攻克指針。

一、複雜聲明讀法:右左法則(優先級法)

從變量名出發,按運算符優先級結合:

聲明解讀步驟含義
int *p;p → * → int指向 int 的指針
int p[3];p → [3] → int含3個 int 的數組
int *p[3];p → [3] → * → int含3個「指向 int 指針」的數組
int (*p)[3];p → * → [3] → int指向「含3個 int 的數組」的指針
int **p;p → * → * → int指向「指向 int 指針」的指針(二級指針)
int (*p)(int);p → * → (int) → int指向「接受int參數、返回int」函數的指針
int *(*p(int))[3];p → (int) → * → [3] → * → int接受int參數、返回「指向含3個int指針數組的指針」的函數

核心口訣:「先看括號,後看右邊,再看左邊」,() 和 [] 優先級高於 *。

二、指針的四大核心屬性

  1. 指針的類型:聲明語句去掉變量名剩下的部分。如 int *ptr 類型是 int*。決定指針自身內存佈局(32位系統佔4字節)及參與運算的規則。
  2. 指針所指向的類型:去掉變量名和最左邊的 * 剩下的部分。如 int *ptr 指向類型是 int。決定解引用 *ptr 時編譯器如何解讀內存位模式。
  3. 指針的值(指向的內存地址):指針變量存儲的數值,被視為地址。指向的內存區大小 = sizeof(指向類型)。
  4. 指針本身佔據的內存:sizeof(指針類型)。32位系統統一4字節。判斷表達式是否為左值的關鍵(有具體內存位置才能作左值)。

區分「指針類型」與「指向類型」是精通指針的關鍵分水嶺。

三、指針運算:以「單元」為單位

char a[20]; int *ptr = (int*)a;
ptr++;  // ptr 值增加 sizeof(int) = 4 字節
  • ptr + n:地址增加 n * sizeof(指向類型) 字節。
  • 指針減指針:同類型指針相減,結果為「相差單元數」(ptrdiff_t),常用於數組索引計算。
  • 指針加指針:非法,無意義。
  • 示例:int *p = arr; p += 5; 移動 5 個 int 單元(20字節),而非 5 字節。

四、指針表達式與左值判斷

結果為指針的表達式即指針表達式。若結果指針已有確定內存位置(如變量、解引用結果),則為左值,可放在賦值號左邊。

int a, b; int *pa=&a, *pb=&b, **pptr=&pa;
&a;        // 右值(臨時地址值)
*ptr = &b; // *ptr 是左值(相當於 pa),可賦值
pa++;      // pa 是左值,可自增

五、指針與數組:深度綁定

  • 數組名 arr 在多數上下文中退化為指向首元素的指針(類型 T*),但 sizeof(arr) 仍得整個數組大小。
  • arr[i] 等價於 *(arr+i),&arr[i] 等價於 arr+i。
  • 指針數組:char *str[3] = {"hello","world","!"};,str 類型 char**,*str 指向第一串。
  • 字符串字面量 是常量數組,類型 const char[],不可修改,賦給 char* 需加 const。
  • 二維數組指針:int (*ptr)[10] = &arr; 指向整個二維數組(或一維數組),ptr+1 跨越一整行。

六、指針與結構體

struct MyStruct { int a,b,c; } ss={20,30,40};
struct MyStruct *ptr = &ss;
ptr->a;  // 等價於 (*ptr).a,推薦用 ->

錯誤示範: int *p = (int*)&ss; *p, *(p+1), *(p+2) 訪問成員。風險: 結構體可能因對齊插入填充字節,導致 p+1 落在填充區而非 b。務必用結構體指針訪問成員。

七、指針與函數

1. 函數指針

int fun1(char*, int);
int (*pfun1)(char*, int) = fun1;
int a = (*pfun1)("abc", 3);  // 調用

2. 指針作函數參數(傳址調用模擬)

int fun(char *s) {  // 統計字符串ASCII和
    int sum=0;
    while(*s) sum += *s++;
    return sum;
}
char str[]="hello";
fun(str);  // 實際傳遞 str 首地址,形參 s 複製該地址,各自佔獨立棧空間

函數內對形參指針自增(s++)不影響實參指針 str。

八、指針類型轉換:強制轉換與整數互轉

1. 指針間轉換

float f=12.3; float *fptr=&f; int *p;
p = (int*)&f;  // 強制轉換:新指針類型 int*,指向類型 int,地址不變
  • (TYPE*)ptr 生成新指針,原指針不變。
  • 函數參數傳遞時隱式構造臨時指針轉換。

2. 整數與指針互轉

unsigned int addr = 0x08000000;
int *ptr = (int*)addr;  // 將整數視作地址
unsigned int val = (unsigned int)ptr;  // 取出地址值

前提: 整數必須代表合法地址,否則解引用導致崩潰。嵌入式寄存器映射常用此技法。

九、指針安全鐵律

// 陷阱1:類型不匹配導致越界寫入
char s='a'; int *ptr=(int*)&s; *ptr=1298;  // 寫入4字節,破壞相鄰3字節!

// 陷阱2:指針運算超出對象邊界
char a; int *ptr=&a; ptr++; *ptr=115;  // 寫入未知內存區!

// 陷阱3:轉換後訪問超出原對象範圍
// 原則:轉換後指向類型尺寸 ≤ 原對象尺寸 → 安全
//       轉換後指向類型尺寸 > 原對象尺寸 → 危險(越界讀寫)

黃金法則:

  1. 指針初始化前不解引用。
  2. 指針運算始終在分配對象範圍內(含結尾 NULL/one-past-end)。
  3. 強制類型轉換後,嚴格按原對象大小訪問,或確保新類型尺寸 ≤ 原尺寸。
  4. 釋放內存後立即置 NULL,防野指針。
  5. 函數參數用 const 修飾不可修改的指向內容。

總結

指針四屬性(類型、指向類型、值、自身內存)是核心心法;運算以指向類型尺寸為單位;數組名退化規則、結構體對齊陷阱、函數指針聲明調用、轉換安全原則,構成工程級指針能力。調試時善用 %p 打印地址、watchpoint 監視內存變化、AddressSanitizer 捕獲越界。指針不難,難在時刻心中有「對象邊界」與「類型契約」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved