知乎 轉載

轉載幾篇知乎上我自己的回答,因爲不喜歡知乎的排版,所以在博客裏重新排版一遍。

原問題:C语言中“.”与“->”有什么区别?

除了表达形式有些不同,功能可以说完全一样阿。那为何又要构造两个功能一样的运算符? 效率有差异?可是现在编译器优化都那么强了,如果真是这样岂不是有些多此一举


刚刚翻了下书,说早期的C实现无法用结构直接当作参数在函数间传递,只能用指向结构的指针在函数间进行传递!我想这应该也是最直观的原因吧。

我的回答

首先 a->b 的含義是 (*a).b ,所以他們是不同的,不過的確 -> 可以用 * . 實現,不需要單獨一個運算符。 嗯,我這是說現代的標準化的 C 語義上來說, -> 可以用 * . 的組合實現。

早期的 C 有一段時間的語義和現代的 C 的語義不太一樣。

稍微有點彙編的基礎的同學可能知道,在機器碼和彙編的角度來看,不存在變量,不存在 struct …

這篇也是源自於水源C板上板友的一個問題,涉及Linux上的控制檯的實現方式和歷史原因。因爲內容比較長,所以在這裏再排版一下發出來。 原帖在這裏

可以設置不帶緩衝的標準輸入流嗎?

WaterElement(UnChanged) 於 2014年12月09日23:29:51 星期二 問到:

請問對於標準輸入流可以設置不帶緩衝嗎?比如以下程序

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    FILE *fp = fdopen(STDIN_FILENO, "r");
    setvbuf(fp, NULL, _IONBF, 0);
    char buffer[20];
    buffer[0] = 0;
    fgets(buffer, 20, fp);
    printf("buffer …

這兩天在飲水思源的C板,關於C++模板的類型轉換的一個討論,後面是我的解答。

原問題

今天在書上看到模板演繹的時候可以允許cast-down,於是我寫了個東西:

template <bool _Test, class _Type = void>
struct enable_if { };

template<class _Type>
struct enable_if<true, _Type> {
    typedef _Type type;
};

class A { };
class B : A { };

template <typename T>
struct …

farseerfc.wordpress.com 導入

3.2 標號、goto,以及switch的實現

goto語句及標號(label)是最古老的C語言特性,也是最早被人們拋棄的語言特性之一。像彙編語言中的jmp指令一樣,goto語句可以跳轉到同一函數體中任何標號位置:

void f()

{int i=0;

Loop: //A label

++i;

if(i<10)goto Loop; //Jump to the label

}

在原始而和諧的早期Fortran和Basic時代,我們沒有if then else,沒有for和while,甚至沒有函數的概念,一切控制結構都靠goto(帶條件的或無條件的)構件。軟件工程師將這樣的代碼稱作“意大利麪條”代碼。實踐證明這樣的代碼極容易造成混亂。

自從證明了結構化的程序可以做意大利麪條做到的任何事情,人們就開始不遺餘力地推廣結構化設計思想,將goto像猛獸一般囚禁在牢籠 …

farseerfc.wordpress.com 導入

3.1 左值右值與常量性(lvalue,rvalue & constant)

首先要搞清楚的是,什麼是左值,什麼是右值。這裏給出左值右值的定義:

1、左值是可以出現在等號(=)左邊的值,右值是隻能出現在等號右邊的值。

2、左值是可讀可寫的值,右值是隻讀的值。

3、左值有地址,右值沒有地址。

根據左值右值的第二定義,值的左右性就是值的常量性——常量是右值,非常量是左值。比如:

1=1;//Error

這個複製操作在C++中是語法錯誤,MSVC給出的錯誤提示爲“error C2106: '=' : left operand must be l-value”,就是說’=’的左操作數必須是一個左值,而字面常數1是一個右值。可見,嚴格的區分左值右值可以從語法分析的角度找出程序的邏輯錯誤。

根據第二定義,一個左值也是一個右值 …

farseerfc.wordpress.com 導入

2.2 I386平臺的內存佈局

衆所周知,I386是32位體系結構。因此對於絕大多數I386平臺的C++編譯器而言,sizeof(int)=sizeof(long)=sizeof(void*)=4。當然C++標準對此沒有任何保證,我們也不應該試圖編寫依賴於此的代碼。

32位指針的可尋址空間爲4GB。爲充分利用這麼大的尋址空間,也是爲了支持其它更先進的技術比如多任務技術或者動態鏈接庫技術,WinNT使用虛擬內存技術,給與每個應用程序全部4GB的內存空間。4GB的地址被一分爲二,前2GB供應用程序自己使用,後2GB由系統內核分配和管理。這2GB的內存地址,通常被劃分成3種內存區使用:

1 代碼及靜態數據區

由代碼加載器從動態鏈接庫鏡像(通常是exe或dll文件)加載,通常定位到鏡像文件中指定的基址開始的內存區。如果基址所在內存已被佔用,動態連接器會將代碼或數據重定向到其它可用地址。

在C++中,靜態數據包括:名字空間(namespace)和全局(global)對象、函數的static對象、類的static數據成員 …

farseerfc.wordpress.com 導入

C++ Tricks

By  FarseerFc

從今天起,我再將在Live SpaceQQZone同時發表一系列文章,暫定名爲“C++Tricks”。

本文旨在記錄和闡述一些本人學習C++時所得的心得、技巧。總體來看,本文涉及的內容是每一個C++程序員都應該知道的,但是很少見諸C++教材。希望對各位同仁學習C++有所幫助。

也可以通過QQ或MSN向我索要此文的DOC版或PDF版,會比網頁上的更新的快一點。

1      詞法問題(Lexical Problems)


2      X86體系結構

     2.1 X86概述

     2.2 …

farseerfc.wordpress.com 導入

2.3 I386平臺C函數內部的棧分配

函數使用棧來保存局部變量,傳遞函數參數。進入函數時,函數在棧上爲函數中的變量統一預留棧空間,將esp減去相應字節數。當函數執行流程途徑變量聲明語句時,如有需要就調用相應構造函數將變量初始化。當執行流程即將離開聲明所在代碼塊時,以初始化的順序的相反順序逐一調用析構函數。當執行流程離開函數體時,將esp加上相應字節數,歸還棧空間。

爲了訪問函數變量,必須有方法定位每一個變量。變量相對於棧頂esp的位置在進入函數體時就已確定,但是由於esp會在函數執行期變動,所以將esp的值保存在ebp中,並事先將ebp的值壓棧。隨後,在函數體中通過ebp減去偏移量來訪問變量。以一個最簡單的函數爲例:

void f()

{

int a=0; //a的地址被分配爲ebp-4

char c=1; //c的地址被分配爲ebp-8

}

產生的彙編代碼爲:

push ebp ;將ebp壓棧

mov ebp,esp ;ebp=esp 用棧底備份棧頂指針

sub …

farseerfc.wordpress.com 導入

2.4 I386平臺C函數調用邊界的棧分配

當調用一個函數時,主調函數將參數以聲明中相反的順序壓棧,然後將當前的代碼執行指針(eip)壓棧,然後跳轉到被調函數的入口點。在被調函數中,通過將ebp加上一個偏移量來訪問函數參數,以聲明中的順序(即壓棧的相反順序)來確定參數偏移量。被調函數返回時,彈出主調函數壓在棧中的代碼執行指針,跳回主調函數。再由主調函數恢復到調用前的棧。

函數的返回值不同於函數參數,通過寄存器傳遞。如果返回值類型可以放入32位變量,比如int、short、char、指針等類型,通過eax寄存器傳遞。如果返回值類型是64位變量,如_int64,同過edx+eax傳遞,edx存儲高32位,eax存儲低32位。如果返回值是浮點類型,如float和double,通過專用的浮點數寄存器棧的棧頂返回。如果返回值類型是用戶自定義結構,或C++類類型,通過修改函數簽名,以引用型參數的形式傳回。

同樣以最簡單的函數爲例:

void f(){

int i …

farseerfc.wordpress.com 導入

2.5 I386平臺的邊界對齊(Align)

首先提問,既然I386上sizeof(int)==4、sizeof(char)==1,那麼如下結構(struct)A的sizeof是多少?

struct A{int i;char c;};

答案是sizeof(A)==8……1+5=8?

呵呵,這就是I386上的邊界對齊問題。我們知道,I386上有整整4GB的地址空間,不過並不是每一個字節上都可以放置任何東西的。由於內存總線帶寬等等的技術原因,很多體系結構都要求內存中的變量被放置於某一個邊界的地址上。如果違反這個要求,重則導致停機出錯,輕則減慢運行速度。對於I386平臺而言,類型爲T的變量必須放置在sizeof(T)的整數倍的地址上,char可以隨便放置,short必須放在2的整數倍的地址上,int必須放在4的整數倍的地址上,double必須放在8的整數倍的地址上。如果違反邊界對齊要求 …

farseerfc.wordpress.com 導入

2.6 I386平臺C函數的可變參數表(Variable Arguments)

基於前文(2.4節)分析,我們可以不通過函數簽名,直接通過指針運算,來得到函數的參數。由於參數的壓棧和彈出操作都由主調函數進行,所以被調函數對於參數的真實數量不需要知曉。因此,函數簽名中的變量聲明不是必需的。爲了支持這種參數使用形式,C語言提供可變參數表。可變參數表的語法形式是在參數表末尾添加三個句點形成的省略號“...”:

void g(int a,char* c,...);

省略號之前的逗號是可選的,並不影響詞法語法分析。上面的函數g可以接受2個或2個以上的參數,前兩個參數的類型固定,其後的參數類型未知,參數的個數也未知。爲了知道參數個數,我們必須通過其他方法,比如通過第一個參數傳遞:

g(3,”Hello”,2,4,5);//調用g並傳遞5個參數,其中後3個爲可變參數。

在函數的實現代碼中,可以通過2.4節敘述的 …

farseerfc.wordpress.com 導入

2.7 I386平臺的其它函數調用模型

上文介紹的只是I386平臺上C函數調用的標準模型,被稱作__cdecl。事實上,Microsoft Visual C++編譯器還支持其它一些函數調用模型,所有調用模型名稱皆以雙下劃線開頭,下面列出所有函數調用模型的異同:

1 __cdecl

參數壓棧順序:逆序(從右至左)

參數堆棧恢復者:主調函數(caller)

__cdecl明確地指出函數使用C函數調用模型,這是默認的調用模型。

2 __stdcall

參數壓棧順序:逆序(從右至左)

參數堆棧恢復者:被調函數(callee)

__stdcall是微軟所謂的標準調用模型。可惜的是它與__cdecl不兼容。幾乎所有的Win32API函數使用這種函數調用模型,希望在DLL之間,或者在程序和WinNT操作系統之間傳遞函數指針的函數也應該使用這種模型。與__cdecl模型的不同之處在於,__stdcall模型下由被調函數恢復堆棧。主調函數在call語句之後,不需要再加上add語句。而被調函數的ret語句則被添加一個參數,代表函數參數堆棧的長度。因此,被調函數需要明確的知曉函數參數的數量和類型,所以在__stdcall模型下不支持可變參數表,所有參數必須寫明 …