1.這樣的初始化有什麼問題?char *p = malloc(10); 編譯器提示“非法初始式” 云云。
答:這個聲明是靜態或非局部變數嗎?函數調用只能出現在自動變數(即局部非靜態變數) 的初始式中。
因為靜態變數的地址必須在編譯的過程中就確定下來而malloc()申請的記憶體位址是在運行時確定的。
2. *p++ 自增p 還是p 所指向的變數?
答:尾碼++ 和-- 操作符本質上比首碼一目操作的優先順序高, 因此*p++ 和*(p++) 等價, 它自增p 並返回p 自增之前所指向的值。
要自增p 指向的值, 使用(*p)++, 如果副作用的順序無關緊要也可以使用++*p。
3 我有一個char * 型指標正巧指向一些int 型變數, 我想跳過它們。為什麼如下的代碼((int *)p)++; 不行?
答:在C 語言中, 類型轉換意味著“把這些二進位位看作另一種類型, 並作相應的對待”; 這是一個轉換操作符,
根據定義它只能產生一個右值(rvalue)。而右值既不能賦值, 也不能用++ 自增。(如果編譯器支援這樣的擴充,
那要麼是一個錯誤, 要麼是有意作出的非標準擴充。) 要達到你的目的可以用:p = (char *)((int *)p + 1);
或者,因為p 是char * 型, 直接用p += sizeof(int);
4.null 指標和未初始化的指標是一回事嗎?
答:null 指標在概念上不同於未初始化的指標:null 指標可以確保不指向任何對象或函數;
而未初始化指標則可能指向任何地方。
5.我可以用0來表示null 指標嗎?
答:根據語言定義, 在指標上下文中的常數0 會在編譯時間轉換為空白指標。也就是說, 在初始化、賦值或比較的時候,
如果一邊是指標類型的值或運算式, 編譯器可以確定另一邊的常數0 為空白指標並產生正確的null 指標值。因此下邊的程式碼片段完全合法:
char *p = 0;
if(p != 0)
然而, 傳入函數的參數不一定被當作指標環境, 因而編譯器可能不能識別未加修飾的0 “表示” 指標。
在函數調用的上下文中產生null 指標需要明確的類型轉換,強制把0 看作指標。
例如, Unix 系統調用execl 接受變長的以null 指標結束的字元指標參數。它應該如下正確調用:
execl("/bin/sh", "sh", "-c", "date", (char *)0);
如果省略最後一個參數的(char *) 轉換, 則編譯器無從知道這是一個null 指標,從而當作一個0 傳入。(注意很多Unix 手冊在這個例子上都弄錯了。)
摘要:
==========================|=============================
|| 可以使用未加修飾的0 | 需要顯示的類型轉換 ||
||------------------------|---------------------------||
|| *初始化 | *函數調用, 範圍內無原型 ||
|| *賦值 | *變參函數調用中的可變參數 ||
|| *比較 | ||
|| *固定參數的函數調用 | ||
|| 且在範圍內有原型 | ||
==========================|=============================
有兩條簡單規則你必須遵循:
1) 當你在源碼中需要null 指標常數時, 用“0” 或“NULL”。
2) 如果在函數調用中“0” 或“NULL” 用作參數, 把它轉換成被調函數需要的指標類型
6. 既然數組引用會蛻化為指標, 如果arr 是數組, 那麼arr 和&arr 又有什麼區別呢?
答:區別在於類型:
在標準C 中, &arr 產生一個“T 型數組” 的指標, 指向整個數組。
在所有的C 編譯器中, 對數組的簡單引用(不包括& 操作符)產生一個T 的指標類型的指標, 指向數組的第一成員。
7. 我如何聲明一個數組指標?
答:通常, 你不需要。當人們隨便提到數組指標的時候, 他們通常想的是指向它的第一個元素的指標。
考慮使用指向數組某個元素的指標, 而不是數組的指標。類型T 的數組蛻變成類型T 的指標, 這很方便;
在結果的指標上使用下標或增量就可以訪問數組中單獨的成員。而真正的數組指標, 在使用下標或增量時, 會跳過整個數組,
通常只在運算元組的數組時有用—— 如果還有一點用的話。如果你真的需要聲明指向整個數組的指標,
使用類似“int (*ap)[N];”這樣的聲明。其中N 是數組的大小。如果數組的大小未知, 原則上可以省略N, 但是這樣產生的類型,
“指向大小未知的數組的指標”, 毫無用處。
8.當我向一個接受指標的指標的函數傳入二維數組的時候, 編譯器報錯了,這是怎麼回事?
答:數組蛻化為指標的規則不能遞迴應用。數組的數組(即C 語言中的二維數組) 蛻化為數組的指標, 而不是指標的指標。
數組指標常常令人困惑, 需要小心對待; 如果你向函數傳遞二位元組:
int array[NROWS][NCOLUMNS];
f(array);
那麼函數的聲明必須匹配:
void f(int a[][NCOLUMNS])
{ ... }
或者
void f(int (*ap)[NCOLUMNS]) /* ap 是個數組指標*/
{ ... }
在第一個聲明中, 編譯器進行了通常的從“數組的數組” 到“數組的指標” 的隱式轉換; 第二種形式中的指標定義顯而易見。
因為被調函數並不為數組分配地址,所以它並不需要知道總的大小, 所以行數NROWS 可以省略。但數組的寬度依然重要,
所以列維度NCOLUMNS (對於三維或多維陣列, 相關的維度) 必須保留。
如果一個函數已經定義為接受指標的指標, 那麼幾乎可以肯定直接向它傳入二維數組毫無意義。
9. 我的strcat() 不行.我試了char *s1 = "Hello, "; char *s2 = "world!";
char *s3 = strcat(s1, s2); 但是我得到了奇怪的結果。
答:這裡主要的問題是沒有正確地為串連結果分配空間。C 沒有提供自動管理的字串類型。
C 編譯器只為源碼中明確提到的對象分配空間(對於字串, 這包括字元數組和串常量)。
程式員必須為字串串連這樣的運行期操作的結果分配足夠的空間,
常可以通過聲明數組或調用malloc() 完成。strcat() 不進行任何分配; 第二個串原樣不動地附加在第一個之後。
因此, 一種解決辦法是把第一個串聲明為數組:
char s1[20] = "Hello, ";
由於strcat() 返回第一個參數的值, 本例中為s1, s3 實際上是多餘的; 在strcat() 調用之後, s1 包含結果。
提問中的strcat() 調用實際上有兩個問題: s1 指向的字串常數, 除了空間不足以放入串連的字串之外,
甚至都不一定可寫。
10. 那麼返回字串或其它集合的正確方法是什麼呢?
答:返回指標必須是靜態分配的緩衝區, 或者調用者傳入的緩衝區,
或者用malloc() 獲得的記憶體, 但不能是局部(自動) 數組。
11. 我有個程式分配了大量的記憶體, 然後又釋放了。但是從作業系統看,記憶體的佔用率卻並沒有回去。
答:多數malloc/free 的實現並不把釋放的記憶體返回作業系統, 而是留著供同一程式的後續malloc() 使用。
12. calloc() 和malloc() 有什麼區別?利用calloc 的零填充功能安
全嗎?free() 可以釋放calloc() 分配的記憶體嗎, 還是需要一個cfree()?
答:calloc(m, n) 本質上等價於:
p = malloc(m * n);
memset(p, 0, m * n);
填充的零是全零, 因此不能確保產生有用的null 指標值或浮點零值free()
可以安全地用來釋放calloc() 分配的記憶體。
13. 我認為我的編譯器有問題: 我注意到sizeof('a') 是2 而不是1 (即,不是sizeof(char))。
答:可能有些令人吃驚, C語言中的字元常數是int 型, 因此sizeof('a') 是sizeof(int),這是另一個與C++ 不同的地方。
14. 為什麼聲明extern int f(struct x *p); 報出了一個奇怪的警告資訊“結構x 在參數列表中聲明”?
答:與C 語言通常的作用範圍規則大相徑庭的是, 在原型中第一次聲明(甚至提到)的結構不能和同一源檔案中的其它結構相容,
它在原型的結束出就超出了作用範圍。要解決這個問題, 在同一源檔案的原型之前放上這樣的聲明:
struct x;
它在檔案範圍內提供了一個不完整的結構x 的聲明, 這樣, 後續的用到結構x的聲明至少能夠確定它們引用的是同一個結構x。
15. 我不明白為什麼我不能象這樣在初始化和數組維度中使用常量:const int n = 5; int a[n];
答:const 限定詞真正的含義是 “唯讀”; 用它限定的對象是運行時 (同常) 不能被賦值的對象。
因此用 const 限定的對象的值並不完全是一個真正的常量。
在這點上 C 和 C++ 不一樣。如果你需要真正的運行時常量, 使用預定義宏 #define(或enum)。
16. 我能否把 main() 定義為 void, 以避免擾人的 “main無傳回值”警告?
答:不能。main() 必須聲明為返回 int, 且沒有參數或者接受適當類型的兩個參數。
如果你調用了 exit() 但還是有警告資訊, 你可能需要插入一條冗餘的 return語句
(或者使用某種 “未到達”指令, 如果有的話)。很多書不負責任地在例子中使用 void main(),
並宣稱這樣是正確的。但他們錯了。
17. #pragma 是什麼, 有什麼用?
答:#pragam 指令提供了一種單一的明確定義的 “救生艙”, 可以用作各種 (不可移植的) 實現相關的控制和擴充:
源碼錶控制、結構壓縮、警告去除 (就像 lint 的老 /* NOTREACHED */注釋), 等等。
18. “#pragma once” 是什麼意思?我在一些標頭檔中看到了它。
答:這是某些前置處理器實現的擴充用於使標頭檔自我識別; 它跟#ifndef技巧等價, 不過移植性差些。