關於C語言的題目

來源:互聯網
上載者:User

很早以前看到,覺得不錯,今天想起來,搜出來備份一下,呵呵 

int x=35;
char str[10];

//問:strlen(str)和sizeof(str)的值分別是多少?
// strlen(str) 值不確定,strlen根據'/0'確定字串是否結束。
// sizeof(str)=10 sizeof一個數組為數組長度

strcpy(str,"www.it315.org"/*共13個字母*/);
//問:此時x和strlen(str)的值分別是多少?
// x 為35
// strcpy(char* dest, const char* src)
// 根據src來複製dest,依照src的'/0'決定複製的長度,而dest必須要提供足夠的長度,這裡會引起溢出,strlen返回13,但是數組外部的資料已經被破壞

//(作者注:我下面給出了更確切的答案 )

str="it315.org";//編譯能通過嗎?
// 數組不能賦值,只能初始化。char str[10] = "it315.org";
// 而且初始化時編譯器會檢查數組的長度與初始化串的長度是否匹配

char *pstr;
strcpy(pstr,"http://www.it315.org");
//上句編譯能通過嗎?運行時有問題嗎?
// 可以通過編譯,但是pstr指向了常量區,運行時最好只做讀操作,寫操作不保險
//(作者注:我下面給出了更確切的答案 )const char *p1;
char * const p2;
//上面兩句有什麼區別嗎?
// const char* 和 char const* 一樣,都是表示指向常量的字元指標。
// char * const 表示指向字元的常量指標

p1=(const char *)str;
//如果是p1=str;編譯能夠通過嗎?明白為什麼要類型轉換?類型轉換的本質是什嗎?
// 可以通過編譯。關於常量與非常量指標的關係是這樣的:
// const指標可以指向const或者非const地區,不會造成什麼問題。
// 非const指標不能指向const地區,會引起錯誤。

strcpy(p1,"abc");//編譯能夠通過嗎?
// 不能通過,strcpy( char*, const char*); char* 不能指向const char*

printf("%d",str);//有問題嗎?
// 沒有問題,輸出的是str的地址資訊。

pstr=3000;//編譯能過嗎?如果不行,該如何修改以保證編譯通過呢?
// 不能通過,char* pstr表示pstr是個字元指標,不能指向3000的整形變數。
// 修改的話,可以這樣:pstr = (char*)3000,把pstr指向3000這個地址;

long y=(long)pstr;//可以這樣做嗎?
// 可以,y的值為pstr所指的地址。不過如果是純粹要地址的話,最好是用unsigned long。

int *p=str;
*p=0x00313200;
printf("%s",str);//會是什麼效果?提示0x31對應字元'1',0x32對應字元'2'。
// 首先編譯未必會過關,有些編譯器可能不允許int * 直接指向char*。最好是改為int *p = (int*)str;
// 過關了效果就是什麼東西都沒有。int *p=str; p為str所指的地址,*p表示修改了str所指向的記憶體。
// 由於sizeof(int)在32位機上,int有4個位元組(其實具體要看編譯器的設定檔,好像是limit.h,一般是4個位元組)所以修改了str[0]-str[3]
// 由於0x00313200頭尾都是0,所以字串為'/0'開頭,什麼都列印不出來。這裡有個Big-endin和little-endin的問題。以0x31323334為例
// little-endin的機器上面,0x31323334在記憶體中排列順序為34 33 32 31,輸出為4321,如INTEL晶片的pc
// big-endin機器上面為31 32 33 34 ,輸出為1234,如IBM POWERPC

p=3000;//p+1的結果會是多少?
// 3000+sizeof(int); 指標+1均為原來地址加上sizeof(指標所指的資料類型)

char *pc=new char[100];//上述語句在記憶體中佔據幾個記憶體塊,怎樣的布局情況?
// 本身pc會佔用函數棧一個4位元組的指標長度(具體是否為4個位元組要看機器和編譯器)。
// new會在堆上申請100個位元組sizeof(char)的連續空間。

void test(char **p)
{
*p=new char[100];
}//這個編譯函數有問題嗎?外面要調用這個函數,該怎樣傳遞參數?
// 該程式沒有問題。需要在函數中對指標所指的地址進行變化是必須傳人指標的地址。
// 原因是這樣的:如果傳入的為指標本身,在函數調用的時候,實參會被複製一個執行個體,這樣就不是原來的指標了,對該指標本身進行的任何改變都不能傳遞迴去了。
// 可以這樣理解,如果傳入的參數為int,那麼對int本身的值的改變就傳不回去啦,加個*也是一樣的。

//能明白typedef int (*PFUN)(int x,int y)及其作用嗎?
// 定義了一個函數指標類型的宏,這樣PFUN就表示指向傳回值為int,且同時帶2個int參數的函數指標類型了。
// 可以用來定義這樣的變數:
// 比如有個函數為int fun( int x, int y );
// PFUN p = fun; //(作者注:我下面給出了更確切的答案) --------------------------------------------------------------------------------------------------------------下面是我的一點補充:
第二題:int x=35;
char str[10]; strcpy(str,"www.it315.org"/*共13個字母*/);
//問:此時x和strlen(str)的值分別是多少? 答:strlen的值為13,在VC++環境下,x的值是要改變的(其他編譯器下沒試,).雖然表面上看來,在程式中並沒有修改x的值,但是實際啟動並執行結果是上面的x的值發生了修改,這是因為strcpy以後,把多餘的資料拷貝進了str的鄰居(int類型的x)中,所以x的資料也就變了.這是一個曾讓我刻骨銘心的問題,在我剛出道時遇到這個問題,雖然在朋友的協助下解決了這個問題,但一直不明白x的值為何變了,只有最後走上培訓教師的崗位,才開始梳理自己曾經的困惑,才開始總結以前的經驗供學員們借鑒.我覺得這個題目的價值非常之大,它能引起學員對字串拷貝越界問題的足夠重視,並且通過這個問題更能明白字串的處理是怎麼回時,更能明白字串與字元數組的關係:字串就是一個字元數組,只是把這個字元數組用在處理串的函數中時,這些函數不考慮數組的長度,只是記住數組的首地址,從首地址開始處理,並在遇到0時結束處理,第四題:char *pstr;
strcpy(pstr,"http://www.it315.org");
//上句編譯能通過嗎?運行時有問題嗎?
答: 編譯可以通過,但是pstr沒有進行有效初始化,它指向了一個不確定的記憶體區,運行時會出現記憶體不可寫錯誤! 最後一題://能明白typedef int (*PFUN)(int x,int y)及其作用嗎? 答:函數指標最大的用處在於它可以被一個模板方法調用,這是我在學java的設計模式時領悟到的.例如,有兩個函數的流程結構完全一致,只是內部調用的具體函數不同,如下所示:void func1(){         //一段流程代碼和面向方面的代理,如安全檢查,日誌記錄等         int sum = add( x , y);        //一段流程代碼和面向方面的代理,如安全檢查,日誌記錄等}void func2(){         //與func1完全相同的一段流程代碼和面向方面的代理,如安全檢查,日誌記錄等         int difference = sub( x , y);        //與func1完全相同的一段流程代碼和面向方面的代理,如安全檢查,日誌記錄等}那麼,可以只定義一個函數,如下所示void func(PFUNC p){         //與func1完全相同的一段流程代碼和面向方面的代理,如安全檢查,日誌記錄等         int difference = p( x , y);        //與func1完全相同的一段流程代碼和面向方面的代理,如安全檢查,日誌記錄等}調用程式在調用時,讓參數p分別指向add和sub函數就可以了.對於其他題目的講解,由於我目前寫作和工作的重點已完全不在c語言方面了,也沒有時間一一解答,就借用Jackie214發布的答案來回應大家.記得以前也看過一篇文章,也把他們貼在一起吧。對張孝祥C語言試題其中一題的探討當《絕對能夠測試你的C語言功力的幾個問題》第一次出現在CSDN首頁時,我就進入了張老師Blog。客觀上說,出的題目比較基礎,但每一題都說出一個所以然來,恐怕不是很簡單。過了幾天就貼出了《語言測試題的講解分析》,我懷著好奇的心情進去看了看。發現裡面讚揚的也有,詆毀的也有。韓愈《師說》裡面講過:聞道有先後,術業有專攻。張老師自然有他的可取之處,也有不知道的知識點。
       OK,言歸正轉,現在開始對試題中的第二題進行探討。題目如下:

int x=35;

char str[10];

strcpy(str,"www.it315.org"/*共13個字母*/);

//問:此時x和strlen(str)的值分別是多少?

我們先不去探討答案是多少,但我覺得這題與編譯器有關。張老師的答案也不是沒有道理,網友秦始皇的回答也有道理。肯定有人開始懷疑了,你到底在說什嗎?這也對,那也對,究竟什麼是對的。好的,我們現在就開始分析。
一、      棧
在具體講解之前,我們先來明確棧的幾個概念:滿棧與空棧,升序棧與降序棧。
       滿棧是指棧指標指向上次寫的最後一個資料單元,而空棧的棧指標指向第一個空閑單元。一個降序棧是在記憶體中反向增長(就是從應用程式空間結束處開始反向增長),而升序棧在記憶體中正向增長。
       RISC機器使用傳統的滿降序棧(FD Full Descending)。如果使用符合X86規定的編譯器,它通常把你的棧指標設定在應用程式空間的結束處並接著使用一個滿降序棧。用來存放一個函數的局部變數、參數、返回地址和其它臨時變數的棧地區稱為棧幀(stack frame)。(關於這部分的詳細資料請參看我另外一篇文章《通過Linux核心源碼看函數調用之前世今生》)。1所示:

圖 1棧幀布局

二、      目標檔案格式
各個系統之間,目標檔案格式都不相同。第一個從貝爾實驗室誕生的UNIX系統使用的是a.out格式。System V Unix的早期版本使用的是COFF(Common Object File Format 一般目標檔案格式)。Windows使用的是COFF的一種變種,叫做PE格式(Portable Executable 可移植可執行)。現代Unix――比如Linux,各種BSD ,以及Sun Solaris――使用的是Unix ELF(Executable and Linkable Format,可執行和可連結格式)。儘管以下的討論集中在ELF上,但不管是哪種格式,基本的概念都是相似的。
如果變數x和str是局部變數,那麼肯定是放在棧中。如果他們兩者都是全域變數,那麼x放在.data段(.data:存放已初始化的全域變數),str放在.bss段(.bss:存放未初始化的全域變數)。
一個典型的ELF可執行目標檔案資訊布局2所示:

圖 2典型的ELF可執行檔格式

       每個程式都有一個運行時儲存空間映像,3所示:

圖 3Linux運行時儲存空間映像

       在Linux系統中,程式碼片段總是從地址0x08048000處開始。資料區段在接下來的下一個4KB對齊的地址處。運行時堆在接下來的讀寫段之後的第一個4KB對齊的地址處,並通過malloc庫往上增長。開始於0x40000000處的段是為共用庫保留的。使用者棧總是從地址0xbfffffff處開始,並向下增長的(向低地址方向增長)。從棧的上部開始於地址0xc0000000處的段是為作業系統駐留儲存空間的部分的代碼和資料保留的。通過這裡講解之後,你應該徹底懂得了滿降序棧的含義。
三、      定址與位元組順序
幾乎在所有的機器上,多位元組對象都以連續的位元組序列存放,對象的地址為所使用的位元組序列中最小的地址。比如,一個int型的變數x的地址為0x100,也就是說&x=0x100,那麼x的四位元組將被儲存在記憶體中的0x100,0x101,0x102和0x103。
某些機器選擇在儲存空間中按照從最低有效位位元組到最高有效位位元組的順序儲存物件,而
另一些機器則按照從最高有效位位元組到最低有效位位元組的順序來儲存物件。前者我們稱為小尾端(little-endian),比如Intel的機器都採用這種規則,後者稱為大尾端(big-endian),如IBM,Motorola等機器。
       假設x類型為int,地址位於0x100處,有一個16進位的值為0x12345678,分別在大尾端和小尾端的儲存方式為:
       大尾端:
                                 0x103                  0x102                0x101               0x100

……

78

56

34

12

……

       小尾端:
                                 0x103                  0x102                0x101               0x100

……

12

34

56

78

……

注意,在字0x12345678中,高位位元組的16進位為0x12,而低位位元組為0x78。不管是在大尾端機器中,還是小尾端機器中,輸出的x的值都為0x12345678。

四、      透過彙編代碼看變數儲存布局
我們從局部變數和全域變數兩個方面來,分別在Windows下的VC++6.0和Linux下的GCC來探討這個題目。
假設程式如下:

1      #include <stdio.h>
2      #include <string.h>
3      
4      int main()
5      {
6           int x = 35;
7           char str[10];
8           strcpy(str,"www.it315.org"/*共13個字母*/);
9           printf("%d/n",x);
10             return 0;
11      }

這段程式在VC++6.0中的反組譯碼代碼如下:

1:    #include <stdio.h>
2:    #include <string.h>
3:
4:    int main()
5:    {
00401010   push        ebp
00401011   mov        ebp,esp
00401013   sub         esp,50h
00401016   push        ebx

   棧幀布局    高地址

前一個棧幀

ebp

(x=35)

……

……

  (str)
   ……

             低地址

00401017   push        esi
00401018   push        edi
00401019   lea          edi,[ebp-50h]
0040101C   mov         ecx,14h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos     dword ptr [edi]
6:        int x = 35;
00401028   mov         dword ptr [ebp-4],23h /*將35壓進棧中*/    (1處)
7:        char str[10];
8:        strcpy(str,"www.it315.org"/*共13個字母*/);
0040102F   push        offset string "www.it315.org" (00420020)
00401034   lea          eax,[ebp-10h]    (2處)
00401037   push        eax
00401038   call         strcpy (00401100)
0040103D   add         esp,8
9:        printf("%d/n",x);
00401040   mov        ecx,dword ptr [ebp-4]    (3處)
00401043   push        ecx
00401044   push        offset string "%d/n" (0042001c)
00401049   call         printf (00401080)
0040104E   add         esp,8
10:       return 0;
00401051   xor         eax,eax
11:   }

從以上代碼可以發現,在1處,將x的值35壓入ebp-4中,在運行2處之前,已經將字串的值壓入棧中了,然後擷取str在棧的地址,即ebp-10h,也就是ebp-16,文中紅色箭頭所指的對方。裝載到eax寄存器中,然後也壓入棧中。眾所周知,在X86平台上,參數的傳遞是通過棧幀來實現的,此時調用函數strcpy,將字串的值拷貝到str的地址處。那麼此時如何存放字串?就是問題的關鍵所在。大家可能都知道已知str字串的地址,那麼要得到它下一個字串的值,就是*(str+1),那麼答案就出來了。在X86平台上,棧是往下增長的,那麼越往高處就是高地址,當進行字串拷貝時,字串的地址順著藍色的線朝上走。因為該字串長度為13,所以覆蓋了x所在的棧中的值,最後一個字元g也就賦給了x,由於在Intel的機器中,採用的是小尾端儲存方式,所以值在棧中的布局4所示:

   棧幀布局    高地址

前一個棧幀

ebp

(x)g

ro.5

31ti

.www
   ……

             低地址

        圖 4字串在棧中的布局

當運行到3處時,程式將ebp-4處的值,也就是x的值壓入棧中,調用printf,所以列印出來的為103(也就是g的值)。
       在Linux環境下,GCC編譯器似乎表現的技高一籌,得到的答案是35。下面我們來看反組譯碼後的代碼:

       .file "sttest.c"
       .section   .rodata
       .align 32
.LC0:
       .string     "www.it315.org"
       .string     ""
.LC1:
       .string     "%d/n"
       .text
.globl main
       .type       main, @function
main:
       pushl       %ebp
       movl       %esp, %ebp
       subl     $56, %esp
       andl      $-16, %esp
       movl       $0, %eax
       subl     %eax, %esp
       movl       $35, -12(%ebp)   /*將35放到棧中,即x處*/    (1處)

   棧幀布局    高地址

前一個棧幀

ebp

……

……

(x=35)

  ……
   ……
   ……
……
……
……
(str)
……

             低地址

       movl       $.LC0, 4(%esp)
       leal      -40(%ebp), %eax      (2處)
       movl       %eax, (%esp)
       call     strcpy
       movl       -12 (%ebp), %eax    (3處)
       movl       %eax, 4(%esp)
       movl       $.LC1, (%esp)
       call     printf
       movl       $0, %eax
       leave
       ret
       .size main, .-main
       .section   .note.GNU-stack,"",@progbits
       .ident      "GCC: (GNU) 3.3.5 (Debian 1:3.3.5-13)"

我們從2處可以發現,str的位置在ebp-40處,取得str在棧中的地址,然後放到堆棧指標處,調用strcpy,此時我們不難發現x的地址和str的地址相差40-12=28,遠遠大於字串的長度,所以根本不可能覆蓋x的值。如果你將字串的長度改為29個字元,那麼就將會覆蓋x的值。
       下面,我們來討論x和str為全域變數的情況,也就是將6,7兩行代碼提到第3行處。
       在第二節中,我們討論過x放在.data段,str放在.bss段,從圖三中可以觀察出,讀寫段(.data,.bss)位於低地址處。對於ELF檔案,一般會規定程式碼片段的總長度大小,低地址處是.data段,因為.data是已經固定了的,而.bss段是在運行時才會賦值,所以程式碼片段剩下的空間都是.bss的大小,注意此時.bss段的地址大於.data段的地址,所以為.bss中的變數賦值時,根本不可能覆蓋.data段的值。把圖2倒過來看,大家就會明白了。所以,如果x和str是全域變數,str的值永遠不可能覆蓋x的值。
五、      總結
其實每一道題目後面都隱藏著很多知識,我們不能只看錶面,大概差不多就行了。只要我們深究下去,可以獲得比題目本身更多的知識點。
六、      附錄
在Windows中,程式運行時儲存空間映像的資料比較少,我到目前只在《編程卓越之道:深入理解電腦》一書中提及過。所以憑我的印象畫下了該圖:

圖 5程式在Windows中運行時的儲存空間映像

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.