西郵Linux興趣小組納新筆試試題
下面是西郵Linux小組今年納新的筆試試題
1、 下面這個程序的輸出結果是什麽?
int main()
{ int a = (1, 2);
printf(“a = %d\n”, a);
return 0;
}
解答:
a = 2
這裏利用了逗號表達式。
2、下面這個程序的輸出結果是什麽?
struct value {
char a[3];
short b;
};
struct value tmp;
printf(“sizeof(tmp) is %d\n”, sizeof(tmp));
解答:
sizeof(tmp) is 6
這裏涉及到字節對齊,a數組後填充了1字節。
3、編寫程序交換a,b的值。(使用多種方法)
4、說明 int *p[5] 和 int (*p)[5] 的區別?
解答:前者是一個指針數組,後者是一個數組指針。
5、編寫函數,實現鏈表的創建,節點的插入和刪除。
6、下面這個程序的輸出結果是什麽?
void f(int *p);
int main(void)
{
int *p;
f(p);
printf(“*p = %d\n”, *p);
return 0;
}
void f(int *p)
{
static int dummy = 5;
p = &dummy;
}
解答:
*p中的值不確定。
大多數人(包括我)都會認為*p的值是5,但這並不正確。因為f函數傳遞的是指針變量,既然是變量,那麽只能通過傳遞地址來確保將f中的值飯或到主函數。如果f傳遞的是&p,那麽最後的結果應該是5。
7、說明如下兩段代碼的區別。
char * p = “xiyou linux”;
char p[] = “xiyou linux”;
解答:
前者p是一個指針,它指向一段只讀字符串。
後者p是一個數組,該數組用“xiyou linux”來初始化。
8、用C語言實現一個n!函數。(要求用遞歸實現)。
9、 char c;
char b[20] = “I love xiyou linux”;
c=’l’與c=”l”有區別麽?若有,區別是什麽?字符串b在內存中占幾個字節?
解答:
c=’l’是一個字符,而c=”l”是字符串。
從對b數組的定義可以得知b在內存中占有20個字節,這與對它進行初始化的字符串長度無關。
如果有如下定義:
char b[] = “I love xiyou linux”;
那麽此時b的大小為19。
10、實現自己的strcat()。
11、char str[20] ;
scanf(“%s”, str);
printf(”%s”,str);
如果輸入:I love linux!<回車>
輸出結果是什麽?為什麽?
解答:
輸出結果為I。因為使用scanf輸入字符串時不能含有空格,否則字符串會從第一個空格處被截斷。
12、下面這段程序的輸出結果是什麽?為什麽?
#define MAX(x,y) x > y ? x : y
int main()
{
int a = 10, b = 20;
int c = 3*MAX(a , b);
printf (“%d\n”, c);
return 0;
}
解答:
經過宏替換後,c = 3*a>b?a:b; 則c的值為10。
13、已知兩個整型數組均為升序排列,將兩個數組合並,且合並後仍按升序排列。
14、下面兩條語句哪條更合理?
if(array[which] == 5 && which < SIZE)…..
if(which < SIZE && array[which] == 5)….
4 條評論 ?
發表在C語言的那些事兒
多維數組那回事兒
2011年2月10日前面幾篇“那回事兒”的文章更強調一維組和指針之間的關系,本文關註的是多維數組,即“數組的數組”。
多維數組
我們可以將多維數組抽象的看作是具有某種類型的一維數組。當“某種類型”為基本的數據類型時,多維數組就退化成普通的一維數組;當“某種類型”仍然為數組時,那麽就形成了多維數組。也就是說任何一個多維數組都可以分解成幾個一維數組。
下面通過示例程序來深入了解多維數組ma[2][3]的構成。
01 |
#include < stdio.h > |
02 |
03 |
int main() |
04 |
{ |
05 |
int ma[2][3]; |
06 |
int (*r)[2][3]; |
07 |
int (*p)[3]; |
08 |
int *t; |
09 |
10 |
/*代碼段1*/ |
11 |
p = ma; |
12 |
printf ( "sizeof(ma[0])=%d\n" , sizeof (ma[0])); |
13 |
printf ( "ma =%p\tp =%p\n" ,ma,p); |
14 |
printf ( "p+1 =%p\n" ,p+1); |
15 |
/*代碼段2*/ |
16 |
r = &ma; |
17 |
printf ( "sizeof(ma)=%d\n" , sizeof (ma)); |
18 |
printf ( "&ma =%p\tr =%p\n" ,&ma,r); |
19 |
printf ( "&ma+1 =%p\tr+1=%p\n" ,&ma+1,r+1); |
20 |
/*代碼段3*/ |
21 |
t = ma[0]; |
22 |
printf ( "sizeof(ma[0][0])=%d\n" , sizeof (ma[0][0])); |
23 |
printf ( "ma[0] =%p\tt =%p\n" ,ma[0],t); |
24 |
printf ( "ma[0]+1 =%p\tt+1 =%p\n" ,ma[0]+1,t+1); |
25 |
return 0; |
26 |
} |
由多維數組ma最左維的長度2可知,ma數組包含兩個元素ma[0]和ma[1]。數組名ma在表達式中是數組ma首元素的首地址。在代碼段1中將ma賦值給數組指針p,則p指向多維數組ma的首元素ma[0],則p+1指向第二個元素ma[1]。其中p是一個數組指針,它指向一個長度為3的數組,則指針p每次移動的偏移量為12。可參考下圖:
在代碼2中對ma取地址並將其賦值給指針r。r現在指向一個“第一維的大小為2,第二維的大小為3的數組”,則r+1將指向下一個這樣的數組(盡管這樣的數組並不存在)。由此也可得知r每次的偏移量為24。
ma[0]和ma[1]都是一個長度為3的整型數組,現在以ma[0]為例進行說明。ma[0]中包含三個元素ma[0][0],ma[0][1]和ma[0][2]。在代碼段3中將ma[0]賦值給t,則t指向數組ma[0]的第一個元素a[0][0],則t+1和t+2則依次指向第二個元素和第三個元素。
對多維數組ma的結構有了一定了解後,現在再看上述程序的運行結果:
01 |
edsionte@edsionte-laptop:~/code/expertC$ gcc array.c -o array |
02 |
edsionte@edsionte-laptop:~/code/expertC$ ./array |
03 |
sizeof (ma[0])=12 |
04 |
ma =0xbfdfaa6c p=0xbfdfaa6c |
05 |
p+1 =0xbfdfaa78 |
06 |
sizeof (ma)=24 |
07 |
&ma =0xbfdfaa6c r=0xbfdfaa6c |
08 |
r+1 =0xbfdfaa84 |
09 |
sizeof (ma[0][0])=4 |
10 |
ma[0]=0xbfdfaa6c t=0xbfdfaa6c |
11 |
t+1 =0xbfdfaa70 |
註意在結果中,p,r和t的值均相同,但是所指向的數據卻不同。更具體的說,這三個指針每次移動時的偏移量不同。
多維數組的初始化
數組的初始化只能在對數組進行聲明(具體為定義型聲明)時進行。一維數組的初始化很簡單,只要將所有初始值放在一個大括號中即可。如果聲明數組時未指定數組的長度,則編譯器會根據初始值的個數來確定數組的長度。
01 |
#include < stdio.h > |
02 |
03 |
int main() |
04 |
{ |
05 |
int m[] = {1,2,3}; |
06 |
int n[] = {1,2,3,}; |
07 |
08 |
printf ( "length(m)=%d\n" , sizeof (m)/ sizeof (m[0])); |
09 |
printf ( "length(n)=%d\n" , sizeof (n)/ sizeof (n[0])); |
10 |
return 0; |
11 |
} |
12 |
13 |
/* 編譯並運行 */ |
14 |
edsionte@edsionte-laptop:~/code/expertC$ gcc init_array.c -o init_array |
15 |
edsionte@edsionte-laptop:~/code/expertC$ ./init_array |
16 |
length(m)=3 |
17 |
length(n)=3 |
註意,在最後一個初始值後面可以繼續加一個逗號也可以省略,這並不影響數組的長度。
對於多維數組而言,通常使用嵌套的大括號進行多維數組的初始化。由於多維的數組其實是有若幹個一維數組構成的,則每個大括號都代表一個一維數組。對於多維數組而言只能省略最左邊 下標的長度。
01 |
#include < stdio.h > |
02 |
03 |
int main() |
04 |
{ |
05 |
int b[][3] = {1,2,1,1}; |
06 |
int c[][3] = {{1,2,1},{1,2,3},}; |
07 |
08 |
printf ( "length(b)=%d\n" , sizeof (b)/ sizeof (b[0])); |
09 |
printf ( "length(c)=%d\n" , sizeof (c)/ sizeof (c[0])); |
10 |
return 0; |
11 |
} |
12 |
13 |
/* 編譯並運行 */ |
14 |
edsionte@edsionte-laptop:~/code/expertC$ gcc init_array.c -o init_array |
15 |
edsionte@edsionte-laptop:~/code/expertC$ ./init_array |
16 |
length(b)=2 |
17 |
length(c)=2 |
可以看到,不使用大括號也可以對多維數組進行初始化,只不過代碼可讀性較差。
它總是迷惑你!
一旦涉及到多維數組,總有些讓你迷惑的地方。比如:
1 |
char ma[2][3][2]={ |
2 |
{{1,2},{2,3},{3,4}}, |
3 |
{{3,5},{4,5},{3,3}} |
4 |
}; |
5 |
6 |
sizeof (ma[0,1,1])=? |
對於上面的代碼,我們最後的迷惑點都可能落在ma[0,1,1]上。難道多維數組可以這樣使用嗎?如果ma[0,1,1]和ma[0][1][1]等價,那麽sizeof(ma[0,1,1])的值就是1。很可惜這樣的猜測是不正確的,正確答案為6。再比如下面的代碼:
1 |
char ma[3][2] = { |
2 |
(1,2),(3,4),(5,3) |
3 |
}; |
4 |
5 |
ma[0][0]=? |
上述代碼是為數組ma進行初始化,那麽ma[0][0]的值是多少?恐怕很多人都會認為是1。不過正確答案是2。
這兩個問題都涉及到了逗號表達式。如果你對逗號表達式有基本的了解,那麽也就沒有上述那種莫名其妙的迷惑了。根據逗號表達式的運算,對於舉例1中的ma[0,1,1]實際上等價於ma[1];對於舉例2中的初始化其實等價為char ma[3][2] = {2,4,3}。
參考:
《C專家編程》 人民郵電出版社;(美)林登(LinDen.P.V.D) 著,徐波 譯;
沒有評論 ?
發表在C語言的那些事兒
指針和數組的可交換性
2011年2月7日指針和數組是不相同的,但“很多時候”我們總認為指針和數組等價的。不可否認,這兩者在某種情況下是可以相互替換的,但並不能就因此而認為在所有情況下都適合。《指針和數組不是一回事兒》系列文章將逐步深入分析指針和數組的不同之處,並解釋什麽時候指數組等價於指針。本文屬於《指針和數組不是一回事兒》系列文章之三。
雖然前面兩篇文章已經說明了數組和指針的不同,但不可否認的是,指針和數組某些可相互交換的用法仍然令人混淆。本文將給出指針和數組可交換的情景,並且分析可交換的原因。
“指針和數組可以交換!”
說出這句話並不是毫無根據的,因為在下面的兩個舉例中使用數組形式和指針形式都可以達到相同的結果。
舉例1:
01 |
#include < stdio.h > |
02 |
03 |
int main() |
04 |
{ |
05 |
char *p = "edsionte" ; |
06 |
char str[] = "edsionte" ; |
07 |
08 |
printf ( "p[1]=%c *(p+1)=%c\n" ,p[1],*(p+1)); |
09 |
printf ( "str[1]=%c *(str+1)=%c\n" ,str[1],*(str+1)); |
10 |
11 |
return 0; |
12 |
} |
13 |
14 |
/* 編譯並運行程序 */ |
15 |
edsionte@edsionte-laptop:~/code/expertC$ gcc tmp.c -o tmp |
16 |
edsionte@edsionte-laptop:~/code/expertC$ ./tmp |
17 |
p[1]=d *(p+1)=d |
18 |
str[1]=d *(str+1)=d |
在舉例1中,指針p指向一個匿名的字符串“edsionte”,這個匿名字符串的占用的內存空間為9個字節;與p指向一個匿名字符串不同,數組str內存儲著字符串“edsionte”,占用了9個字節的空間。
現在分別要訪問’d’,則方法如下。對於指針p,分別可以通過指針形式*(p+1)和數組形式p[1]來訪問其所指的數據;對於數組str,分別可以通過指針形式*(str+1)和數組形式str[1]來訪問數組內的元素。
我們已經知道指針和數組在內存構造和訪問方式上都不同,但為什麽它們都分別可以通過指針的方式和數組的方式進行訪問?
舉例2:
01 |
#include < stdio.h > |
02 |
03 |
void getStr_pointer( char *str) |
04 |
{ |
05 |
printf ( "%s\n" ,str); |
06 |
printf ( "getStr_pointer(): sizeof(str)=%d\n" , sizeof (str)); |
07 |
} |
08 |
09 |
void getStr_array( char str[100]) |
10 |
{ |
11 |
printf ( "%s\n" ,str); |
12 |
printf ( "getStr_array(): sizeof(str)=%d\n" , sizeof (str)); |
13 |
} |
14 |
15 |
int main() |
16 |
{ |
17 |
char str[] = "I am edsionte!" ; |
18 |
19 |
getStr_pointer(str); |
20 |
getStr_array(str); |
21 |
printf ( "main(): sizeof(str)=%d\n" , sizeof (str)); |
22 |
} |
23 |
24 |
/* 編譯並運行程序 */ |
25 |
edsionte@edsionte-laptop:~/code/expertC$ gcc tmp2.c -o tmp2 |
26 |
edsionte@edsionte-laptop:~/code/expertC$ ./tmp2 |
27 |
I am edsionte! |
28 |
getStr_pointer(): sizeof (str)=4 |
29 |
I am edsionte! |
30 |
getStr_array(): sizeof (str)=4 |
31 |
main(): sizeof (str)=15 |
在舉例2中,getStr_pointer函數和getStr_array函數的功能都是顯示一條字符串。但不同的是,前者傳入的參數是一個指針,後者傳入的參數是一個數組。在主函數中分別調用這兩個函數,傳入的參數都是數組str。
既然數組和指針不同,但為什麽作為函數的形參,char str[ ]和char *str相同?
上述舉例所引出的這兩個問題正是本文討論的重點,它們分別對應著“指針和數組是相同”的兩種情況。下面將分別進行討論。
1.表達式中的數組名就是指針
表達式中的數組名其實就是數組首元素的首地址。對於編譯器而言,a[i]其實就是*(a+i)的形式,因此以數組形式訪問數組元素總是可以寫成“數組首元素首地址加上偏移量”的形式。取下標符號[ ]其實可以看成一種運算規則,即指向T類型的指針和一個整數相加,最終產生的結果類型為T。這裏的指針就為數組首元素首地址,而整數即為數組的偏移量。
這裏必須說明一下偏移量,它是指針每次移動的步長。對於數組而言,偏移量即數組元素的大小;對於指針而言,它的偏移量即為指針所指類型的大小。在對指針進行移動時,編譯器負責計算每次指針移動的步長。
因此,str[i]和*(str+i)兩種形式其實是等價的。因為編譯器總是將數組形式的訪問自動轉換成指針形式的訪問。上面的分析都是針對數組而言,其實對指針以數組和指針形式訪問的原理也是如此。只不過此時的訪問是對指針所指向數據的訪問。
結合數組和指針訪問方式的不同,下面對舉例1的代碼做詳細分析:
1.1.以指針的形式和以數組的形式訪問數組
從符號表中得到符號str的地址即為數組首元素的首地址。
- 以數組的形式:str[1]。從符號表中得到str符號的地址,即數組首元素的首地址;編譯器將數組形式轉化為*(str+1),在首元素首地址上加一個偏移量得到新地址;從這個新地址中讀取數據,即為’d’;
- 以指針的形式:*(str+1)。從符號表中得到str的地址,即數組首元素的首地址;在此地址上加一個偏移量得到新地址;從這個新地址中讀取數據,即為’d’;
1.2.以指針的形式和以數組的形式訪問指針
不管以何種方式訪問,我們應該清楚p始終是一個指針。從編譯器符號表中得到符號p的地址為指針p的地址。
- 以指針的形式:*(p+1)。首先從符號表中得到p的地址;從該地址中得到指針p;對指針p加上1個偏移量得到新地址;從這個新地址中讀取數據,即為’d’;
- 以數組的形式:p[1]。首先從符號表中得到p的地址;從該地址中得到指針p;編譯器將數組形式轉化成*(p+1),對p加一個偏移量得到新地址;從這個新地址中讀取新數據,即為’d’;
分析至此,你應該了解到以數組形式和以指針形式訪問只是寫法上的不同而已,其本質對內存的訪問過程是一樣的。
2.作為函數參數的數組名等同於指針
當作為函數形參時,編譯器會將數組改成指向數組首元素的指針。此時的數組就等價於指針。之所以將傳遞給函數的數組形參轉化為指針是處於效率的考慮。
在C語言中,所有非數組的實參數據都是以傳值形式傳遞給函數的,即將實參的一份拷貝傳遞給調用函數中的形參,調用函數對這份拷貝(也就是形參)的修改不影響實參本身的值。如果按照這樣的道理,傳遞數組時就必須拷貝整個數組空間,這樣必然會產生很大的開銷。並且,大部分時候並不會訪問到數組中所有的元素而只是其中的幾個。考慮到上述的原因,數組作為實參傳遞給調用函數時,只需將數組名傳遞給函數即可;而形參會被編譯器該成指針的形式。因此,作為形參的數組既可以寫成數組也可以寫成指針。
現在再回到舉例2中的代碼,對於形參中的char str[]和char *str也就感到不再奇怪了。事實上,即便將形參寫成char str[]或char str[100],編譯器仍然會將它們改成char *str的形式。
既然任何數組作為形參時候都等價於一個指針,那麽在函數內對“數組”的一切操作都等價於對指針的操作。驗證這一點的很好例證就是舉例2中對數組str求長度。在主函數中,sizeof(str)的值為15,這個結果毫無爭議,它就是數組str的長度。而在getStr_pointer()和getStr_array()中,sizeof(str)的值都為4,也就驗證了作為形參的數組str在調用函數中就是一個指針!在上述情況1中,雖然表達式中數組名也被認為是指針,但是數組仍然是數組(main函數中sizeof的結果就是很好的驗證),而此部分數組就是指針。這也是數組等價於指針的唯一情況。
換句話說,雖然在將數組作為形參的函數中,你可以繼續以數組的形式使用這個參數,但實際上你跟不可能找到數組的蹤影!
總結
關於指針和數組之間的異同需要反復的思考和總結,才能搞清關系。下面對指針和數組之間的可交換性再作義簡單的總結。
1.在表達式中以a[i]這樣的形式對數組進行訪問時,編譯器總將其解釋為*(a+i)的形式;
2.在數組作為函數的形參時,編譯器將數組改寫成指針,這個指針即為數組首元素的首地址。這也是數組等價指針的唯一情形;
3.由於2的原因,一個數組作為函數的形參時,既可以將數組定義成數組,也可以將數組定義成指針;
4.指針和數組永遠是兩碼事,因此在不同文件中的聲明和定義必須匹配,但卻始終都能寫成指針的形式和數組的形式(這完全是寫法的不同)。
參考:
《C專家編程》 人民郵電出版社;(美)林登(LinDen.P.V.D) 著,徐波 譯;
《C語言深度解剖》北京航空航天大學出版社;陳正沖 著;
沒有評論 ?
發表在C語言的那些事兒
指針和數組的訪問方式
2011年2月5日指針和數組是不相同的,但“很多時候”我們總認為指針和數組等價的。不可否認,這兩者在某種情況下是可以相互替換的,但並不能就因此而認為在所有情況下都適合。《指針和數組不是一回事兒》系列文章將逐步深入分析指針和數組的不同之處,並解釋什麽時候指數組等價於指針。本文屬於《指針和數組不是一回事兒》系列文章之二。
前文從內存結構的角度說明了指針和數組的不同,本文將以訪問方式的角度再次說明指針和數組的不同。先看下面的代碼:
1 |
char str[] = "edsionte" ; |
2 |
char *p = "edsionte" ; |
當編譯完程序後,程序中的標示符都有一個地址,所有標示符的地址形成一個符號表。
數組的訪問方式
以數組str為例,如果要訪問str[1],即數組str的第二個元素,則它的訪問步驟如下:
1.從編譯器的符號表中得到str的地址,比如0x0000FE00。這個地址即為數組str首元素的首地址;
2.在這個地址上加一個偏移量得到新的地址0x0000FE01;
3.從這個新地址中讀取數據;
通過下圖可以加深對數組訪問方式的理解:
指針的訪問方式
以上述代碼中的指針p為例,如果要訪問*(p+1),即指針p所指向的匿名字符串的第二個字符,則它的訪問步驟如下:
1.從編譯器符號表中得到指針p的地址,比如0x0000EE20。這個地址即為&p,也就是指向指針p的指針;
2.從地址0x0000EE20中讀取它內容即為指針p,比如0x00F0A000;
3.在0x00F0A000的基礎上加一個偏移量,得到新地址0x00F0A001;
4.讀取0x00F0A001中的內容,即為指針p所指的數據;
通過下圖可以近一步理解指針的訪問方式:
通過分析得知,在符號表中得到的是指針的地址而不是我們所要訪問的指針;而在符號表中可以直接得到數組首元素的首地址。因此,訪問指針時必須先通過符號表中指針的地址得到所要訪問的指針,再接著進行指針所指內容的訪問;而數組則直接可以通過符號表中的地址進行元素訪問。也就是說指針的訪問比數組的訪問多了一次對內存地址的讀取。
一不小心就引發的錯誤
現在看下面的兩段代碼:
01 |
/* 代碼段1 */ |
02 |
file1: |
03 |
char str[] = "edsionte" ; |
04 |
file2: |
05 |
extern char *str; |
06 |
/* 代碼段2 */ |
07 |
file1: |
08 |
char *p == "edsionte" ; |
09 |
file2: |
10 |
extern char str[]; |
對於代碼段1,在文件1中定義了數組str,而在文件2中將str聲明為指針;對於代碼段2,在文件1中定義了指針p,而在文件2中將p聲明為數組。這裏的聲明指的是外部引用型聲明,定義指的是定義型聲明。
不管是上述那種情形,編譯的時候都會出現錯誤。從上述對指針和數組訪問方式的分析中可以得知,一個標示符被聲明成指針還是數組對其訪問方式影響巨大。下面我們對這兩種錯誤作詳細分析。
定義為數組,聲明為指針
在文件2中,既然str被聲明成指針,那麽就應當按照指針的方式進行訪問。首先從符號表中得到指針str的地址;從該地址中讀取4個字節的數據即為指針str;接下來根據指針str訪問其所指向的數據。這個過程好像很順利,不過對於文件1中的數組str,其訪問過程又是怎樣的?文件1和文件2的訪問結果是否一致?下圖可幫助你理解。
從上圖可以看到,str在文件2中被聲明成指針,那麽就符號表中str的地址0x0000FE00會被當作指針str的地址。根據指針的訪問方式,必須從這個地址中取出指針p。雖然以0x0000FE00為首的四個字節中存儲的是“edsi”,但是它們一律會被當成地址,按十六進制表示即為0x65647379。即便可以訪問到這個地址,但是從這個地址中按照char型取出的數據並不是我們想要的。
由於編譯器對每個文件進行單獨編譯,文件2並不知道str在文件1中被定義成什麽類型。str在文件1中被定義成數組,那麽就應該按照數組的方式訪問數據;str在文件2中被聲明成指針,那麽就應該按照數組的方式訪問數據。因此,在兩個不同的文件中分別將str定義成數組而聲明成指針會出現對str訪問不一致的現象,所以編譯器會產生錯誤。
定義為指針,聲明為數組
此時,對於這種情況的理解也就簡單多了。由於在文件而中p被聲明成數組,因此就應該按照數組的方式對其進行訪問。編譯器會將原本指針str的地址當作str數組首元素的首地址,再對其加相應偏移量進行訪問。這顯然也是不合理的,因此編譯器產生錯誤。具體可參見下圖:
對上述的錯誤進行分析後,我們應該清楚將一個標示符聲明成數組,編譯器就會按照數組的訪問方式去訪問它;指針也是如此。因此,應該在多個文件中保持聲明和定義相匹配。
指針和數組的其他區別
指針和數組除了在內存構造和訪問方式上不同外,還有一些其他的區別。
1.指針通常用於指向一個動態的數據結構,而數組則用於存儲固定大小和數據類型相同的數據;
2.指針所指向的數據通過malloc()分配,並且需要free()釋放;而數組本身的內存空間則是隱士分配和釋放,也就是在定義數組的時候進行;
3.指針所指向的數據通常是匿名的,而數組名則是數組所占內存空間的名字;
在本系列的最後一篇文章中,我們將分析指針和數組易被混淆的根源——也可將其稱為指針和數組的可交換性。
參考:
《C專家編程》 人民郵電出版社;(美)林登(LinDen.P.V.D) 著,徐波 譯;
《C語言深度解剖》北京航空航天大學出版社;陳正沖 著;
沒有評論 ?
發表在C語言的那些事兒
指針和數組的內存構造
2011年2月1日
指針和數組是不相同的,但“很多時候”我們總認為指針和數組等價的。不可否認,這兩者在某種情況下是可以相互替換的,但並不能就因此而認為在所有情況下都適合。《指針和數組不是一回事兒》系列文章將逐步深入分析指針和數組的不同之處,並解釋什麽時候指數組等價於指針。本文屬於《指針和數組不是一回事兒》系列文章之一。
指針和數組的本質是什麽,這是本文討論的重點。從內存結構的角度來說,兩者是截然不同的兩個概念。
數組的聲明與定義
關於C語言中的聲明,《聲明那回事兒》一文中已詳細敘述。這裏再具體針對數組的定義和聲明做以分析。以下面的聲明代碼為例:
1 |
char str[10]; |
2 |
extern char str[]; |
第一條聲明語句定義了一個char型的數組,並為其分配了100字節大小的內存空間。而第二條聲明語句則是為了告訴編譯器這個數組的類型以及大小。由於在外部引用型聲明中並不會數組分配內存空間,因此這種聲明並不需要指定數組的大小。對於多維數組也並不需要指定第一維的大小。
指針的內存布局
指針本質上就是一個內存地址,為了方便使用這個內存地址將它和一個標示符綁定在一起。比如:
1 |
int i = 10; |
2 |
int *p = &i; /* 假設變量i的內存地址為0x000F3E00 */ |
上述語句將地址0x000F3E00和p綁定在一起,並且一旦綁定就不能再修改,p此時也被稱為指針變量,簡稱指針。“指針變量“中的“變量”並不是說明p可以再和其他地址綁定,而是強調與p綁定的這個地址中的內容可變,即i的值可以變化。
既然指針p是一個內存地址,那麽在32位的系統中指針p所占的內存大小就始終為4字節。雖然整型變量i也占4字節,但是這兩個同大小的內存空間卻有著本質區別。指針p只能存放內存地址,並且這個地址只能是整型數據的首地址。即使在p內存放了其他數據,也會一律被當作內存地址來處理。
通過下圖可以近一步了解指針和其所指數據的關系:
從圖中可得知,不管指針所指數據占多大的內存空間,指針本身只占用4字節的大小。由於指針p本身占用4字節的內存空間,因此這部分內存空間也必然會有首地址。通過&p操作就可以得到指針p的首地址,也就是存儲指針p的內存空間的首地址。從上圖中可以看到指針p中即為整型變量i的首地址,因此我們也稱p是一個指向整型變量i的指針。
數組的內存布局
數組是一塊連續的內存空間,這塊內存空間的名稱即為數組名。比如:
1 |
int a[100]; |
當定義了一個具體的數組a時,編譯器就根據數據類型和大小為其分配100*sizeof(int)大小的內存空間,並將這塊連續的內存空間命名為a。雖然我們可以通過a[i]這種方式來訪問元素i,但這並不代表a[i]就是這個元素的名稱。因此每個數組元素實際上是沒有名字的,編譯器只為這塊內存提供了唯一的名稱a。同時數組名a也代表數組首元素的首地址。數組的內存結構如下:
通過對指針和數組內存布局的分析,我們可以得知這兩者完全是不相同的。指針不管指向什麽數據,它本身的大小就是4個字節(32位系統);而數組則是一塊連續的內存空間。在下文中,將會從訪問方式的角度分析指針和數組的不同。
參考:
《C專家編程》 人民郵電出版社;(美)林登(LinDen.P.V.D) 著,徐波 譯;
《C語言深度解剖》北京航空航天大學出版社;陳正沖 著;
西郵Linux興趣小組納新筆試試題