水滴石穿C語言之編譯器引出的問題

2021-03-31 08:56:58 字數 3200 閱讀 2993

基本解釋問題:c檔案的分別編譯

我有乙個陣列a定義在f1.c中,但是我想在f2.c中計算它的元素個數,用sizeof可以達到這個目的嗎?

答案與分析:

答案是否定的,你沒有辦法達到目的,本質原因是sizeof操作符只是在「編譯時(***pile time)」起作用,而c語言的編譯單位是每次單個.c檔案進行編譯(其它語言也都如此)。因此,sizeof可以確定同乙個原始檔中某個陣列的大小,但是對於定義在另乙個原始檔中的陣列它無能為力了,因為那已經是「執行時(run time)」才能確定的事情了。

一件事情要想做,總會有辦法的,下面提供有三種可選的辦法來解決這個問題:

1)、定義乙個全域性變數,讓它記住陣列的大小,在另外乙個.c檔案中我們通過訪問這個全域性變數來得到陣列的大小資訊(好像有點小題大做得不償失^_^)。

2)、在某個.h檔案中用巨集定義陣列的大小,例如#define array_size 50,然後在兩個原始檔中都包含這個.h檔案,通過直接訪問array_size來得到定義在不同.c檔案中的陣列的大小。

3)、設定陣列的最後乙個元素為特殊值,例如0,-1,null等,然後我們通過遍歷陣列來尋找這個特殊的結尾元素,從而判斷陣列的長度(這個辦法效率低,也是笨笨的)。

問題:函式返回值隱含傳遞指標

下面的**可以正常工作,但是在程式結束時會有乙個致命錯誤產生。究竟是什麼原因呢?

struct list

main (argc, argv)

答案與分析:

原因很簡單,稍微注意一點不難發現,在定義結構list的右花括弧後面加乙個分號就可以解決這個問題:

struct list

;//缺了這個分號可不行!

好了,問題是解決了,但,你知道這個錯誤究竟導致了什麼致命問題嗎?問題不是表面上那麼簡單的,ok,讓我們來看看事情背後的真相。

首先看一看下面這段**:

void func ( struct my_struct stx)

struct my_struct sty = ;

func (sty);

當呼叫函式func的時候,是把結構變數sty的值拷貝乙份到呼叫棧中,從而作為引數傳遞給函式func的,這個叫做c語言的引數值傳遞。我相信這個你一定很清楚,那麼,你應該知道:如果函式的返回值是結構變數的話,函式應該如何將值返回給呼叫者呢?且看下面這段**:

struct my_structfunc (void)

struct my_struct sty = func();

此時函式func的返回值是乙個結構型別的值,這個返回值被放在記憶體中乙個陰暗恐怖的地方,同時安排了乙個指標指向這個地方(暫時稱為「神秘指標」),而這個指標會由c語言的編譯器作為乙個隱藏引數傳遞給函式func。當函式func返回時,編譯器生成的**將這個由隱藏指標指向的記憶體區的值拷貝到返回結構sty中,從而完成將結構變數值返回給呼叫者。

你明白了上述所講的東東,那麼今天問題的真正原因也就呼之欲出了:

因為struct list 的定義後面沒有加分號,導致主函式main (argc, argv)被編譯器理解為是乙個返回值為結構變數的函式,從而期望得到除了argc和argv以外的第三個引數,也就是我們上面提到的那個隱含傳入的「神秘指標」。可是,大家知道,這裡函式是main函式,main函式的引數是由程式中的啟動**(startup code)提供的。而啟動**當然認為main()天生就應該只得到兩個引數,要「神秘指標」,當然沒有,如此一來, main()在返回時自作主張地去呼叫棧中訪問它的那個並不存在的第三個引數(即神秘指標),這樣導致非法訪問,產生致命問題。這才是這個問題的真正根源。

建議:

1)、盡量將結構變數的指標而不是結構本身作為函式引數,否則函式呼叫時記憶體拷貝的開銷可不小,尤其是對那些呼叫頻繁、結構體大的情況。

2)、結構定義的後面一定要加分號,經過上面我的大段講述,我相信你不會犯相同的錯誤。

問題:編譯器會給函式的引數隱含製造臨時副本

請問執行下面的test函式會有什麼樣的結果?

void getmemory2(char **p, int num)

void test(void)

答案與分析:

這是林銳的《c/c++高質量程式設計指南》上面的例子,拿來用一下。

這樣呼叫會產生如下兩個後果:

1)、能夠輸出hello

2)、記憶體洩漏

另乙個相關問題:

請問執行test函式會有什麼樣的結果?

void getmemory(char *p)

void test(void)

答案與分析:

後果嚴重,執行的結果是程式崩潰,通過執行除錯我們可以看到,經過getmemory後,test函式中的 str仍舊是null。可想而知,一呼叫

strcpy(str, "hello world");

程式必然崩潰了事。

原因分析:

c編譯器總是會為函式的每個引數製作臨時副本,指標引數p的副本是 _p,編譯器使 _p = p。如果函式體內的程式修改了_p的內容,就導致引數p的內容作相應的修改。這就是指標可以用作輸出引數的原因。在本例中,_p申請了新的記憶體,只是把_p所指的記憶體位址改變了,但是p絲毫未變。所以函式getmemory並不能輸出任何東西,如果想要輸出動態記憶體,請使用指向指標的指標,或者,使用指向引用的指標。

問題:標頭檔案和包含它的.c檔案一同編譯問

下面的**非常短小,看起來毫無問題,但編譯器會報告乙個錯誤,請問問題可能出現在什麼地方?

#include "someheader.h"

int myint = 0;

答案與分析:

不用盯著int myint = 0;看,這一句賦值應該是c語言中最簡單的語句,問題肯定不會出在它身上,那麼問題只可能出現在someheader.h中,最常見的就是該標頭檔案的最後一行的宣告(函式也好,變數也好)沒有用分號";"結尾,那麼編譯器會將它和myint變數結合起來考慮,自然就會出錯了。

這個問題主要是提醒你,在定位問題時思路要拓寬一點,可能要考慮一下所包含的標頭檔案是否有問題。

結論:被包含的標頭檔案是和.c檔案一起編譯的,標頭檔案中的問題會反映到.c檔案編譯中去的,切記。

水滴石穿C語言之編譯器引出的問題

問題 c檔案的分別編譯 我有乙個陣列a定義在f1.c中,但是我想在f2.c中計算它的元素個數,用sizeof可以達到這個目的嗎?答案與分析 答案是否定的,你沒有辦法達到目的,本質原因是sizeof操作符只是在 編譯時 compile time 起作用,而c語言的編譯單位是每次單個.c檔案進行編譯 其...

水滴石穿C語言之static辨析

1 概述 static 宣告的變數在c語言中有兩方面的特徵 1 變數會被放在程式的全域性儲存區中,這樣可以在下一次呼叫的時候還可以保持原來的賦值。這一點是它與堆疊變數和堆變數的區別。2 變數用static告知編譯器,自己僅僅在變數的作用範圍內可見。這一點是它與全域性變數的區別。2 問題 static...

水滴石穿C語言之static辨析

1 概述static 宣告的變數在 c語言中有兩方面的特徵 1 變數會被放在程式的全域性儲存區中,這樣可以在下一次呼叫的時候還可以保持原來的賦值。這一點是它與堆疊變數和堆變數的區別。2 變數用 static 告知編譯器,自己僅僅在變數的作用範圍內可見。這一點是它與全域性變數的區別。2 問題 stat...