int *p = NULL;
定義了 p是一個指針。
p這個指針的步長是4,也就是 ++p後,p的內存地址會增加4個byte。
p的初始值是NULL,NULL的定義一般是(void *)0或者0.
如果p是全局變量,那麼內存單元在link時分配在靜態存儲區;如果是局部變量,那麼內存單元在運行時分配在stack上。
定義指針時,編譯器並不爲指針所指向的對象分配空間,它只是分配指針本身的空間,除非在定義時同時賦給指針一個字符串常量進行初始化。
eg.
char *p = "breadfruit";
注意只有對字符串常量纔是如此。另外,在ANSI C中,初始化指針時所創建的字符串常量被定義爲只讀。如果試圖通過指針修改這個字符串的值,程序就
會出現未定義的行爲。該字符串常量,一般存放在數據段,但在有些編譯器中,存放在文本段,以防止它被修改。
與指針相反,由字符串常量初始化的數組是可以被修改的。
[Parts are referred from <<C Expert Programming>>]
2。常見用法
(1). 訪問內存數據。
從底層的觀點來看,一個指針就是一個內存地址。
(2). 動態分配時,儲存分配出的內存地址。
eg.
char *p = NULL;
p = (char *)malloc(MAX_MEM_SIZE);
(3). 在函數中作爲參數傳入或傳出,在函數內訪問傳入數據
eg.
int gid_name(char *name, gid_t *gid)
{
[...]
*gid = ptr->gid = gr->gr_gid;
return (0);
}
如上函數,返回值只用以標記錯誤條件。
使用指針類型參數有時也是爲了避免函數調用時的大量數據copy,比如結構體,這時使用指針會更有效率。
eg.
static double diffsec(struct timeval *now, struct timeval *then)
{
return ((now->tv_sec - then->tv_sec)*1.0 + (now->tv_usec - then->tv_usec) / 1000000.0);
}
函數中通過指針傳入的兩個結構體的成員值計算結果。
像上面這種情況,當通過指針傳入的參數不需被修改時,最後在前面添上const關鍵字,表示在函數作用域內,該參數只讀。
(4). 數據成員訪問
訪問結構體的成員:p->f;
訪問數組元素:
p = &a[0];
*(p + 3) = 0;
(5). 替代數組作爲函數參數
在C中,當數組名作爲函數參數時,傳入的實際上是這個數組的第一個元素的地址, 即,注意當數組作爲函數的參數進行傳遞時,該數組自動退化爲同類型的指針。這時,函數中任何對數組的改動將直接影響到該數組本身。
同樣,C函數中return的數組名也會只返回一個指向該數組第一個元素的指針。所以,當從函數中返回一個數組時,要確保該數組不是從函數中定義的
局部變量,局部變量內存分配在棧上,否則,一旦函數退出,這個數組的內容會被覆蓋,返回的數據會因此無效。避免這種情況的一個方法是將該數組定義爲static。
eg.
char *inet_ntoa(struct in_addr ad)
{
unsigned long int s_ad;
int a, b, c, d;
static char addr[20];
d = s_ad % 256;
s_ad /= 256;
c = s_ad % 256;
s_ad /= 256;
b = s_ad % 256;
a = s_ad / 256;
sprintf(addr, "%d.%d.%d.%d", a, b, c, d);
return addr;
}
如上函數,如果不把數組addr聲明爲static,當函數inet_ntoa() return時,addr數組的內容會無效。
(6). 函數指針
C不允許函數作爲參數傳入其他函數中,但允許傳遞一個函數指針給另一個函數。
(7). 作爲別名
eg.
struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
[...]
struct output *out1 = &output;
在上例中,指針變量out1可以用在使用變量output的地方。
一般這樣使用有三個原因:
i. 提高效率
如果一個數據結構比較大,那麼在某些場合,比如賦值語句中使用指針會避免對整個結構體的拷貝。
static struct termios cbreakt, rawt, *curt;
[...]
curt = useraw ? &rawt : &cbreakt;
ii. 訪問static的數據
一個指針常用來指向不同的靜態數據,
eg.
char *s;
[...]
s = *(opt->bval) ? "True" : "False";
iii. 在不同的函數中訪問同一個全局數據
eg.
static long rntb[32] =
{
3, 0x9a319039, 0x32d9c024, 0x9b663182, 0x5da1f342,
[...]
};
[...]
{
[...]
fptr = &state[rand_sep];
[...]
}
{
[...]
*fptr += *rptr;
i = (*fptr >> 1) & 0x7fffffff;
本例中,fptr被初始化指向一個隨機數種子表,然後在函數srrandom()中再次被賦值,最後,在rrandom函數中被用來修改所指向的數據。
size_t strlen(const char *str)
{
register const char *s;
return(s - str);
}
(9). 直接內存訪問
既然指針本質上表現爲一個內存地址,那麼在底層代碼中會使用指針去訪問特定硬件的內存地址。
static void vid_wrchar(char c)
{
volatile unsigned short *video = NULL;
vid_ypos * 80 + vid_xpos;
*video = (*video & 0xff00) | 0x0f00 | (unsigned short)c;
}
注意,現代操作系統一般不允許直接硬件訪問,所以一般這樣的代碼只會出現在嵌入式系統或者內核和驅動程序中。
這些規則主要是爲了防止內存泄漏的
(1). 用malloc申請內存之後,應該立即檢查指針值是否爲NULL。防止使用指針值爲NULL 的內存。
char *p = (char *)malloc(USER_DEFINE_SIZE);
if (NULL == p)
{
/* Exception Handle */
}
(2). 爲數組和動態內存賦初值。防止將未被初始化的內存作爲右值使用。
For Array:
int aVar[100] = { 0 };
char *p = NULL; /* Initial */
p = (char *)malloc(USER_DEFINE_SIZE); /* Malloc buffer */
if (NULL == p)
{
/* Exception Handle */
}
memset(p, 0, USER_DEFINE_SIZE);
(3). 避免數組或指針的下標越界,特別要當心發生“多1”或者“少1”操作。
(4). 動態內存的申請與釋放必須配對,防止內存泄漏。
For free allocate memory:
if (NULL != p)
{
free(p);
}
(5). 用free釋放了內存之後,立即將指針設置爲NULL,防止產生“野指針”。