本文地址:http://www.cnblogs.com/archimedes/p/c-point-misuse-safeproblem.html,轉載請注明源地址。
考慮如下的聲明:
int* ptr1, ptr2; // ptr1為指針,ptr2為整數
正確的寫法如下:
int* ptr1, *ptr2;
用類型定義代替宏定義是一個好的習慣,類型定義允許編譯器檢查作用域規則,而宏定義不一定會。
使用宏定義輔助聲明變量,如下所示:
#define PINT int* PINT ptr1, ptr2;
不過結果和前面所說的一致,更好的方法是使用下面的類型定義:
typedef int* PINT; PINT ptr1, ptr2;
在使用指針之前未初始化會導致運行時錯誤,如下面的代碼:
int* p; ... printf("%d\n", *p);
指針p未被初始化,可能含有垃圾數據
把指針初始化為NULL更容易檢查是否使用正確,即便這樣,檢查空值也比較麻煩,如下所示:
int *pi = NULL; ... if(pi == NULL) { //不應該解引pi } else { //可以使用pi }
我們可以使用assert函數來測試指針是否為空值:
assert(pi != NULL);
緩沖區溢出
緩沖區溢出是指當計算機向緩沖區內填充數據位數時超過了緩沖區本身的容量,使得溢出的數據覆蓋在合法數據上,理想的情況是程序檢查數據長度並不允許輸入超過緩沖區長度的字符,但是絕大多數程序都會假設數據長度總是與所分配的儲存空間相匹配,這就為緩沖區溢出埋下隱患。操作系統所使用的緩沖區又被稱為"堆棧".。在各個操作進程之間,指令會被臨時儲存在"堆棧"當中,"堆棧"也會出現緩沖區溢出。
下面幾種情況可能導致緩沖區的溢出:
訪問數組元素時沒有檢查索引值
對數組指針做指針算術運算時不夠小心
用gets這樣的函數從標准輸入讀取字符串
誤用strcpy和strcat這樣的函數
使用malloc這樣的函數的時候一定要檢查返回值,否則可能會導致程序的非正常終止,下面是一般的方法:
float *vector = malloc(20 * sizeof(float)); if(vector == NULL) { //malloc分配內存失敗 } else { //處理vector }
聲明和初始化指針的常用方法如下:
int num; int *pi = #
下面是一種看似等價但是錯誤的聲明方法:
int num; int *pi; *pi = #
參見《C迷途指針》
沒有什麼可以阻止程序訪問為數組分配的空間以外的內存,下面的例子中,我們聲明並初始化了三個數組來說明這種行為:
#include<stdio.h> int main() { char firstName[8] = "1234567"; char middleName[8] = "1234567"; char lastName[8] = "1234567"; middleName[-2] = 'X'; middleName[0] = 'X'; middleName[10] = 'X'; printf("%p %s\n", firstName, firstName); printf("%p %s\n", middleName, middleName); printf("%p %s\n", lastName, lastName); return 0; }
運行結果如下:
下圖說明了內存分配情況:
將數組傳給函數時,一定要同時傳遞數組長度,這個信息幫助函數避免越過數組邊界
#include<stdio.h> void replace(char buffer[], char replacement, size_t size) { size_t count = 0; while(*buffer && count++ < size) { *buffer = replacement; buffer++; } } int main() { char name[8]; strcpy(name, "Alexander"); replace(name, '+', sizeof(name)); printf("%s\n", name); return 0; }
其中一個例子是試圖檢查指針邊界但方法錯誤
#include<stdio.h> int main() { int buffer[20]; int *pbuffer = buffer; for(int i = 0; i < sizeof(buffer); i++) { *(pbuffer++) = 0; } return 0; }
改為:i < sizeof(buffer) / sizeof(int);
有界指針是指指針的使用被限制在有效的區域內,C沒有對這類指針提供直接的支持,但是可以自己顯示地確保。如下所示:
#define SIZE 32 char name[SIZE]; char *p = name; if(name != NULL) { if(p >= name && p < name + SIZE) { //有效指針,繼續 } else { //無效指針,錯誤分支 } }
一種有趣的變化是創建一個指針檢驗函數;
下面的代碼定義一個函數消除無效指針:
int valid(void *ptr) { return (ptr != NULL); }
下面的代碼依賴於_etext的地址,定義於很多的類linux操作系統,在windows上無效:
#include <stdio.h> #include <stdlib.h> int valid(void *p) { extern char _etext; return (p != NULL) && ((char*) p > &_etext); } int global; int main(void) { int local; printf("pointer to local var valid? %d\n", valid(&local)); printf("pointer to static var valid? %d\n", valid(&global)); printf("pointer to function valid? %d\n", valid((void *)main)); int *p = (int *) malloc(sizeof(int)); printf("pointer to heap valid? %d\n", valid(p)); printf("pointer to end of allocated heap valid? %d\n", valid(++p)); free(--p); printf("pointer to freed heap valid? %d\n", valid(p)); printf("null pointer valid? %d\n", valid(NULL)); return 0; }
在linux平台運行結果如下:
pointer to local var valid?
1
pointer to
static
var valid?
1
pointer to function valid?
0
pointer to heap valid?
1
pointer to end of allocated heap valid?
1
pointer to freed heap valid?
1
null
pointer valid?
0
另一種方法是利用ANSI-C和C++的邊界檢查工具(CBMC)
優點就是任何類型的指針都占4個bit(32位機)
而其他的形式不一定,float占4個,double占8個,char[1000]占1000個。
在函數中使用指針傳遞可以減少內存使用.....
本人剛剛開始學,直覺到了這麼一點點區別。
不要見笑
第一章。指針的概念
指針是一個特殊的變量,它裡面存儲的數值被解釋成為內存裡的一個地址。要搞清一個指針需要搞清指針的四方面的內容:指針的類型,指針所指向的類型,指針的值或者叫指針所指向的內存區,還有指針本身所占據的內存區。讓我們分別說明。
先聲明幾個指針放著做例子:
例一:
(1)int *ptr;
(2)char *ptr;
(3)int **ptr;
(4)int (*ptr)[3];
(5)int *(*ptr)[4];
如果看不懂後幾個例子的話,請參閱我前段時間貼出的文章<<如何理解c和c
++的復雜類型聲明>>。
1。 指針的類型。
從語法的角度看,你只要把指針聲明語句裡的指針名字去掉,剩下的部分就是這個指針的類型。這是指針本身所具有的類型。讓我們看看例一中各個指針的類型:
(1)int *ptr; //指針的類型是int *
(2)char *ptr; //指針的類型是char *
(3)int **ptr; //指針的類型是 int **
(4)int (*ptr)[3]; //指針的類型是 int(*)[3]
(5)int *(*ptr)[4]; //指針的類型是 int *(*)[4]
怎麼樣?找出指針的類型的方法是不是很簡單?
2。指針所指向的類型。
當你通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了編譯器將把那片內存區裡的內容當做什麼來看待。
從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉,剩下的就是指針所指向的類型。例如:
(1)int *ptr; //指針所指向的類型是int
(2)char *ptr; //指針所指向的的類型是char
(3)int **ptr; //指針所指向的的類型是 int *
(4)int (*ptr)[3]; //指針所指向的的類型是 int()[3]
(5)int *(*ptr)[4]; //指針所指向的的類型是 int *()[4]
在指針的算術運算中,指針所指向的類型有很大的作用。
指針的類型(即指針本身的類型)和指針所指向的類型是兩個概念。當你對C越來越熟悉時,你會發現,把與指針攪和在一起的“類型”這個概念分成“指針的類型”和“指針所指向的類型”兩個概念,是精通指針的關鍵點之一。我看了不少書,發現有些寫得差的書中,就把指針的這兩個概念攪在一起了,所以看起書來前後矛盾,越看越糊塗。
3。 指針的值,或者叫指針所指向的內存區或地址。
指針的值是指針本身存儲的數值,這個值將被編譯器當作一個地址,而不是一個一般的數值。在32位程序裡,所有類型的指針的值都是一個32位整數,因為32位程序裡內存地址全都是32位長。
指針所指向的內存區就是從指針的值所代表的那個內存地址開始,長度為sizeof(指針所指向的類型)的一片內存區。以後,我們說一個指針的值是XX,就相當於說該指針指向了以XX為首地址的一片內存區域;我們說一個指針指向了某塊內存區域,就相當於說該指針的值是這塊內存區域的首地址。
指針所指向的內存區和指針所指向的類型是兩個完全不同的概念。在例一中,指針所指向的類型已經有了,但由於指針還未初始化,所以它......余下全文>>