首先,我們先來看下面這個經典的**:
int main()從這段**裡你看到了什麼問題?我們都知道,這段程式裡少了乙個#include 還少了乙個return 0;的返回語句。
不過,讓我們來深入的學習一下,
這段**在c++下無法編譯,因為c++需要明確宣告函式
這段**在c的編譯器下會編譯通過,因為在編譯期,編譯器會生成乙個printf的函式定義,並生成.o檔案,鏈結時,會找到標準的鏈結庫,所以能編譯通過。
但是,你知道這段程式的退出碼嗎?在ansi-c下,退出碼是一些未定義的垃圾數。但在c89下,退出碼是3,因為其取了printf的返回值。為什麼printf函式返回3呢?因為其輸出了』4′, 』2′,』\n』 三個字元。而在c99下,其會返回0,也就是成功地執行了這段程式。你可以使用gcc的 -std=c89或是-std=c99來編譯上面的程式看結果。
另外,我們還要注意main(),在c標準下,如果乙個函式不要引數,應該宣告成main(void),而main()其實相當於main(…),也就是說其可以有任意多的引數。
我們再來看一段**:
#include<
stdio.h
>
void f(void)
int main(void)
這個程式會輸出什麼?
我相信你對a的輸出相當有把握,就分別是4,5,6,因為那個靜態變數。
對於c呢,你應該也比較肯定,那是一堆亂數。
但是你可能不知道b的輸出會是什麼?答案是1,2,3。為什麼和c不一樣呢?因為,如果要初始化,每次呼叫函式裡,編譯器都要初始化函式棧空間,這太費效能了。但是c的編譯器會初始化靜態變數為0,因為這只是在啟動程式時的動作。
全域性變數同樣會被初始化。
說到全域性變數,你知道 靜態全域性變數和一般全域性變數的差別嗎?是的,對於static 的全域性變數,其對鏈結器不可以見,也就是說,這個變數只能在當前檔案中使用。
我們再來看乙個例子:
#include<
stdio.h
>
void foo(void)
void bar(void)
int main(void)
你知道這段**會輸出什麼嗎?a) 乙個隨機值,b) 42。a 和 b都對(在「在函式外訪問區域性變數的乙個比喻」文中的最後給過這個例子),不過,你知道為什麼嗎?
如果你使用一般的編譯,會輸出42,因為我們的編譯器優化了函式的呼叫棧(重用了之前的棧),為的是更快,這沒有什麼***。反正你不初始化,他就是隨機值,既然是隨機值,什麼都無所謂。
但是,如果你的編譯開啟了**優化的開關,-o,這意味著,foo()函式的**會被優化成main()裡的乙個inline函式,也就是說沒有函式呼叫,就像巨集定義一樣。於是你會看到乙個隨機的垃圾數。
下面,我們再來看乙個示例:
#include<
stdio.h
>
int b(void)
int c(void)
int main(void)
這段程式會輸出什麼?,你會說是,3,4,7。但是我想告訴你,這也有可能輸出,4,3,7。為什麼呢? 這是因為,在c/c++中,表達的評估次序是沒有標準定義的。編譯器可以正著來,也可以反著來,所以,不同的編譯器會有不同的輸出。你知道這個特性以後,你就知道這樣的程式是沒有可移植性的。
我們再來看看下面的這堆**,他們分別輸出什麼呢?
示例一
int a=41; a++; printf("%d\n", a);
示例二
int a=41; a++ & printf("%d\n", a);
示例三
int a=
41; a++ && printf("%d\n", a);
示例四
int a=41; if (a++
<
42) printf("%d\n", a);
示例五
int a=41; aa
= a++; printf("%d\n", a);
只有示例一,示例三,示例四輸出42,而示例二和五的行為則是未定義的。關於這種未定義的東西又叫sequence points,因為這會讓編譯器不知道在乙個表示式順列上如何訪問變數的值。比如a = a++,a + a++,不過,在c中,這樣的情況很少。
下面,再看一段**:(假設int為4位元組,char為1位元組)
struct x ;printf("%d,", sizeof(struct x));
struct y ;
printf("%d\n", sizeof(struct y));
這個**會輸出什麼?
a) 9,10
b)12, 12
c)12, 16
答案是c,我想,你一定知道位元組對齊,是向4的倍數對齊。
但是,你知道為什麼要位元組對齊嗎?還是因為效能。因為這些東西都在記憶體裡,如果不對齊的話,我們的編譯器就要向記憶體乙個位元組乙個位元組的取,這樣一來,struct x,就需要取9次,太浪費效能了,而如果我一次取4個位元組,那麼我三次就搞定了。所以,這是為了效能的原因。
但是,為什麼struct y不向12 對齊,卻要向16對齊,因為char d; 被加在了最後,當編譯器計算乙個結構體的尺寸時,是邊計算,邊對齊的。也就是說,編譯器先看到了int,很好,4位元組,然後是 char,乙個位元組,而後面的int又不能填上還剩的3個位元組,不爽,把char b對齊成4,於是計算到d時,就是13 個位元組,於是就是16啦。但是如果換一下d和c的宣告位置,就是12了。
另外,再提一下,上述程式的printf中的%d並不好,因為,在64位下,sizeof的size_t是unsigned long,而32位下是 unsigned int,所以,c99引入了乙個專門給size_t用的%zu。這點需要注意。在64位平台下,c/c++ 的編譯需要注意很多事。你可以參看《64位平台c/c++開發注意事項》。
下面,我們再說說編譯器的warning,請看**:
#include<
stdio.h
>
int main(void)
考慮下面兩種編譯**的方式 :
cc -wall a.c
cc -wall -o a.c
前一種是不會編譯出a未初化的警告資訊的,而只有在-o的情況下,再會有未初始化的警告資訊。這點就是為什麼我們在makefile裡的cflags上總是需要-wall和 -o。
最後,我們再來看乙個指標問題,你看下面的**:
#include<
stdio.h
>
int main(void)
第一條printf語句應該沒有問題,就是 bfe2e100
第二條printf語句你可能會以為是bfe2e101。那就錯了,a+1,編譯器會編譯成 a+ 1*sizeof(int),int在32位下是4位元組,所以是加4,也就是bfe2e104
第三條printf語句可能是你最頭疼的,我們怎麼知道a的位址?我不知道嗎?可不就是bfe2e100。那豈不成了a==&a啦?這怎麼可能?自己存自己的?也許很多人會覺得指標和陣列是一回事,那麼你就錯了。如果是 int *a,那麼沒有問題,a == &a。但是這是陣列啊a,所以&a其實是被編譯成了 &a[0]。
第四條printf語句就很自然了,就是bfe2e114。
看過這麼多,你可能會覺得c語言設計得真拉淡啊。不過我要告訴下面幾點dennis當初設計c語言的初衷:
1)相信程式設計師,不阻止程式設計師做他們想做的事。
2)保持語言的簡潔,以及概念上的簡單。
3)保證效能,就算犧牲移植性。
今天很多語言進化得很高階了,語法也越來越複雜和強大,但是c語言依然光芒四射,dennis離世了,但是c語言的這些設計思路將永遠不朽。
深入理解C語言 深入理解指標
關於指標,其是c語言的重點,c語言學的好壞,其實就是指標學的好壞。其實指標並不複雜,學習指標,要正確的理解指標。指標也是一種變數,占有記憶體空間,用來儲存記憶體位址 指標就是告訴編譯器,開闢4個位元組的儲存空間 32位系統 無論是幾級指標都是一樣的 p操作記憶體 在指標宣告時,號表示所宣告的變數為指...
深入理解C語言 深入理解指標
關於指標,其是c語言的重點,c語言學的好壞,其實就是指標學的好壞。其實指標並不複雜,學習指標,要正確的理解指標。指標也是一種變數,占有記憶體空間,用來儲存記憶體位址 指標就是告訴編譯器,開闢4個位元組的儲存空間 32位系統 無論是幾級指標都是一樣的 p操作記憶體 在指標宣告時,號表示所宣告的變數為指...
C語言指標深入理解
前幾天看了乙個程式,裡面一段關於指標的 讓我非常糾結,看了很久才看懂,在這裡將將其記錄下來,希望能對大家有一定的幫助,先看示例程式 編譯器gcc include include include typedef struct list node list node,list,plist node st...