C語言深度解剖讀書筆記之——C語言基礎測試題



轉載http://blog.csdn.net/feitianxuxue/article/details/7334929


前幾天天看到這本書,感覺不錯,在看之前,先做了後面的習題,結果只得了60多分,一直以爲自己的基礎還是不錯的,做完後對了答案後,感覺自己的自信心一下全沒有了,不過遇到問題解決問題,我用了2天時間好好研讀了這本書,感覺真不錯。雖然感覺都是一些基礎的知識,但是我讀的還是津津有味,感覺收穫蠻多的,感謝這本書的作者陳正衝。呵呵,說來我本科專業和這位大牛還是同一個專業呢,呵呵。不是隻有計算機科班出身的才能學好編程,知真正的高手都是自學的。

今天就把我當時做錯的題目和認爲比較好的題目一個個寫出來。再次分析下

如果我在哪家公司遇到類似這種題目我會感覺這家公司出題很有水平,重基礎,真正理解C語言的人才能得高分。注重細節,知其然知其所以然。

題目1.

下面代碼有什麼問題,爲什麼?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6.       char string[10],str1[10];   
7.       int i;   
8.       for (i=0;i<10;i++)   
9.       {   
10.              str1[i] = 'a';   
11.       }   
12.       strcpy(string,str1);   
13.       cout<<string<<endl;   
14.       system("pause");   
15.       return 0;   
16.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) {    char string[10],str1[10];    int i;    for (i=0;i<10;i++)    {       str1[i] = 'a';    }    strcpy(string,str1);    cout<<string<<endl;
    system("pause");    return 0; }

運行的時候回出現下面情況:

error1.exe 中的 0xcccc6161 處未處理的異常: 0xC0000005: Access violation

做這個題目的時候,我也知道字符串strcpy是以'\0'爲結束標識的。會產生數組越界,但是表達不清楚。

答案:運行到strcpy的時候可能會產生內存異常。

因爲:str1沒有結束符標識,str1數組後面繼續存儲的可能不是'\0',而是亂碼。Cout函數,對於輸出char*類型,順序打印字符串中的字符直到遇到空字符('\0')或打印了由精度指定的字符數爲止。

 

題目2.

下面代碼的結果是多少?爲什麼?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6.    char a[1000];   
7.    int i;   
8.    for (i=0;i<1000;i++)   
9.    {   
10.        a[i] = -1-i;   
11.    }   
12.    cout<<strlen(a)<<endl;   
13.     
14.    system("pause");   
15.    return 0;   
16.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) {    char a[1000];    int i;    for (i=0;i<1000;i++)    {    a[i] = -1-i;    }    cout<<strlen(a)<<endl;    system("pause");    return 0; }

做題目的時候沒有考慮到字符溢出,自然做錯了

分析:

我們知道計算機底層只認識0,1,所以任何數據到了底層都會通過計算轉換成0,1,那麼負數是怎樣存儲呢?由於“-”無法存入內存,我們把它做個標記,把基本數據類型的最高位騰出來,用來存符號,同時約定如下:如果最高位是1,表明這個數是負數,其值爲除最高位以外的剩餘位的值添上這個“-”號。

 一個32位的signed int 類型整數,其值表示的範圍爲:-8位的char,其值表示的範圍爲。一個32位的unsigned int 類型整數,其值表示的範圍爲:8位的unsigned char,其值表示的範圍爲。需要說明的是,在默認情況下,編譯器默認的數據位signed類型。

for循環內,當i的值爲0時,a[0]的值爲-1.在計算機系統中,數值一律用補碼來表示。在用兩個補碼的數相加時,如果最高位(符號位)有進位,則進位被捨棄。正數的補碼與其原碼一致;負數的補碼:符號位爲1,其餘位爲該數絕對值的原碼按位取反,然後整個數+1.

-1的補碼爲0xff,-2的補碼爲0xfe......

i的值爲127時,a[127]的值爲-128,而-128char類型數據能表示的最小的負數。

i繼續增加,a[128]的值肯定不能使-129,因爲這時候發生了溢出。-129需要9位(1 1000 0001)才能存儲下來,而char只有8位,所以最高位被丟棄,剩下的8位是原來9位補碼(1 111 1111)的低8位的值,即0x7f。當i繼續增加到255時,-256的補碼低8位全爲0,然後當i增加到256時,-257的補碼的低8位全爲1,即低8位的補碼爲0xff,如此又開始一輪新的循環......

按照上面的分析,a[0]~a[254]裏面的值都不爲0,而a[255]的值我0.strlen函數是計算字符串長度的,並不包含字符串最後的'\0'。判斷一個字符串是否結束的標誌就是看是否遇到'\0';如果遇到'\0',則認爲字符串結束。

由此分析,strlen(a)的值爲255

 

題目3.

下面的兩段代碼有什麼區別?什麼時候需要使用代碼(2

代碼(1):

int j = 10;

int j = i;

int k =i;

代碼(2):

volatile int i = 10;

int j = i;

int k = i;

基礎知識:

Volatile關鍵字和const一樣,是一種類型修飾符,用它修飾的變量表示可以被某些編譯器未知的因素改變,比如操作系統、硬件或者其他線程。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。

int j = 10;

int j = i;   //1)語句

int k =i;   //2)語句

此時編譯器對代碼進行優化,這是因爲在(1)(2)兩條語句中,i沒有被用作左值(沒有被賦值)。這時候編譯器認爲i的值沒有發生改變,所以在(1)語句時從內存中取出i的值賦給j之後,這個值並沒有丟棄掉,而是在(2)語句時繼續用這個值給k賦值。編譯器不會生成出彙編代碼重新從內存裏取i的值,這樣提高了效率。但是要注意的是:(1)(2)語句之間確認i沒有被用作左值才行。

volatile int i = 10;

int j = i;  //3)語句

int k = i;  //4)語句

volatile 關鍵字告訴編譯器,i是隨時可能發生變化的,每次使用它的時候必須從內存中取出i的值,因而編譯器生成的彙編代碼會重新從i的地址處讀出數據放在k中。

這樣看來,如果i是一個寄存器變量,表示一個端口數據或者是多個線程的共享數據,那麼就容易出錯,所以volatile 可以保證對特殊地址的穩定訪問。

 

題目4.

在32位的x86系統下,輸出的值是多少?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6.    int a[5] = {1,2,3,4,5};   
7.    int *ptr1 = (int *)(&a+1);   
8.    int *ptr2 = (int *)((int)a+1);   
9.    cout<<hex<<ptr1[-1]<<endl<<hex<<*ptr2<<endl;   
10.    system("pause");   
11.    return 0;   
12.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) {    int a[5] = {1,2,3,4,5};    int *ptr1 = (int *)(&a+1);    int *ptr2 = (int *)((int)a+1);    cout<<hex<<ptr1[-1]<<endl<<hex<<*ptr2<<endl;    system("pause");    return 0; }

運行結果:5   2000000

分析:這裏需要注意的是&a是整個數組的首地址,a是數組首元素的首地址,其值相同,但是意義不同。

對於指針p+1,指針變量與一個整數相加減並不是用指針變量裏的地址直接加減這個整數,這個整數的單位不是字節而是元素的個數。

知道了這些就好辦了。

int *ptr1 = (int *)(&a+1);

&a+1這下加的1跑到了a[5](雖然不存在)ptr1[-1]=*(ptr1-1),這下跑到了a[4],所以結果是5

int *ptr2 = (int *)((int)a+1);

a是數組a的首元素的首地址。(int)a+1是元素a[0]的第二個字節的地址。由於是int型,所以佔4個字節

大端模式:

       a[0]                a[1]              a[2]                   a[3]  

        1                      2                  3                      4

0x00 0x00 0x00 0x01    0x00 0x00 0x00 0x02    0x00 0x00 0x00 0x03    0x00 0x00 0x00 0x04   

在32位的x86系統下,是小端模式——認爲第一個字節是最低位字節

在內存中存放爲:

a[0]                     a[1]

0x01 00 00 00     0x02 00 00 00

所以讀取爲:0x02000000

答案爲:2000000

 

題目5.

假設p的值爲0x100000,如下表達式的值分別爲多少?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6. struct Test   
7. {   
8.     int Num;   
9.     char *pcName;   
10.     short sDate;   
11.     char cha[2];   
12.     short sBa[4];   
13. }*p;   
14. p = (Test *)0x100000;   
15. cout<<hex<<(p+0x1)<<endl;   
16. cout<<hex<<((unsigned long)p+0x01)<<endl;   
17. cout<<hex<<((unsigned int *)p+0x01)<<endl;   
18. system("pause");   
19. return 0;   
20.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) { struct Test { int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }*p; p = (Test *)0x100000; cout<<hex<<(p+0x1)<<endl; cout<<hex<<((unsigned long)p+0x01)<<endl; cout<<hex<<((unsigned int *)p+0x01)<<endl; system("pause"); return 0; }

運行結果爲:

這裏還是記住一句話:指針p+1,指針變量與一個整數相加減並不是用指針變量裏的地址直接加減這個整數,這個整數的單位不是字節而是元素的個數。

p+0x1的值爲0x100000+sizeof(Test)*0x01=0x100014

(unsigned long)p+0x01這裏涉及到強制類型轉換,將指針變量p保存的值強制轉換成無符號的長整形數。任何數值一旦被強制轉換,其類型就變了,所以這個表達式其實就是一個無符號的長整形數加上另一個整數,其值爲0x100000+0x1=0x100001

(unsigned int *)p+0x01,這裏p被強制轉換成一個指向無符號整形的指針,所以其值爲:0x100000+sizeof(unsigned int)*0x01=0x100004

 

題目6.

下面代碼輸出的結果是多少?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6. int a[3][2] = {(0,1),(2,3),(4,5)};   
7. int *p;   
8. p = a[0];   
9. cout<<p[0];   
10. system("pause");   
11. return 0;   
12.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) { int a[3][2] = {(0,1),(2,3),(4,5)}; int *p; p = a[0]; cout<<p[0]; system("pause"); return 0; }

運行結果:1

這裏需要注意的是{}裏面是逗號表達式,int a[3][2] = {(0,1),(2,3),(4,5)};相當於

int a[3][2] = {1,3,5};

 

題目7.

下面的代碼有什麼問題?爲什麼?

1.#include    
2.using namespace std;   
3.   
4.struct student   
5.{   
6. char *name;   
7. int score;   
8.}stu,*pstu;   
9.   
10.int _tmain(int argc, _TCHAR* argv[])   
11.{   
12. pstu = (struct student *)malloc(sizeof(struct student));   
13. strcpy(pstu->name,"Jimy");   
14. pstu->score = 99;   
15. free(pstu)+;   
16. system("pause");   
17. return 0;   
18.}   

#include using namespace std; struct student { char *name; int score; } stu,*pstu; int _tmain(int argc, _TCHAR* argv[]) { pstu = (struct student *)malloc(sizeof(struct student)); strcpy(pstu->name,"Jimy"); pstu->score = 99; free(pstu)+; system("pause"); return 0; }

運行錯誤:

error7.exe 中的 0x5b33f689 (msvcr90d.dll) 處未處理的異常: 0xC0000005: 寫入位置 0xcdcdcdcd 時發生訪問衝突

爲指針變量pstu分配了內存,但是沒有給name指針分配內存。

在爲結構體指針分配內存的時候,是從外向裏,即先分配結構體的指針,再分配成員指針,釋放的時候,是從裏向外,先釋放成員指針,再釋放結構體指針,順序不能錯。

正確的修改應該是:

pstu = (struct student *)malloc(sizeof(struct student));

pstu->name = (char *)malloc(20);

修正:由於疏忽,這道題沒寫完,再次把修正後的答案,貼出來,以免誤導讀者,下面是一個貼友的答案,謝謝! 

1.int main(int argc, char* argv[])     
2.{     
3.       pstu = (struct student *)malloc(sizeof(struct student));     
4.      pstu->name = (char *)malloc(20);     
5.      strcpy(pstu->name,"Jimy");     
6.      pstu->score = 99;   
7.      free(pstu->name);   
8.      pstu->name = NULL;   
9.      free(pstu);     
10.      pstu = NULL;   
11.       system("pause");     
12.      return 0;     
13.}   

發佈了112 篇原創文章 · 獲贊 162 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章