顯示具有 C Programming 標籤的文章。 顯示所有文章
顯示具有 C Programming 標籤的文章。 顯示所有文章

2012年3月9日 星期五

Introduction of GCC Compiler

對軟體工程師來說,使用 GCC 編譯程式碼是一項基本的技能,GCC 可說與我們的工作內容密不可分,以下就 GCC 的大致功能做一些簡單的整理:
(主要內容參考自 MAXSOLAR'S LINUX BLOG - Makefile 範例教學)
  • 傳統的編譯方式
  • gcc foo.c -o foo
    可在拆解成如下的動作:
    gcc foo.c -c
    gcc foo.o -o foo
    • 編譯的過程是將原始碼(foo.c)先使用 -c 參數編譯成 Object file(foo.o),然後鏈結一個函式庫成為二進位檔(binary)如下。[-c : Compile]
    • gcc foo1.c $SACLIB/sacio.a -O3 -g -Wall -ansi -o foo1
    • 接下來有更多 Makefile 的參數出現了,一一介紹如下:
      • -c:編譯但不進行鏈結,會產生一個跟原始碼相同名稱但副檔名為.o的目的檔。
      • -O:表示最佳化的程度,-O預設就是-O1,可以指定成-O2或-O3,數字越大表示最佳化程度越好,但是會增加編譯時間。
      • -g:把偵錯資訊也編譯進去,當你有需要使用GDB軟體進行偵錯,必須加入-g使GDB能夠讀取。一般情況下可以不用-g,因為它也會增加binary大小。
      • -Wall:顯示警告訊息,使用這個參數會在編譯時顯示更多的警告訊息。這個參數相當有用,特別是找不到 libs/headers 之類的問題。
      • -ansi:使用相容ANSI標準的編譯方式,ANSI 是 American National Standards Institute,即美國國家標準學會的簡稱。-ansi 可以增加程式的可移植性。
      • $SACLIB:是一個路徑變數名稱,必須被指定正確的值。執行這個命令前必須先確定這個變數是有被指派到正確路徑才行。.a 檔是一個靜態函式(static library),關於靜態跟共享的觀念稍候解釋。
    • 再來更多吧!假設今天要編譯 main 這隻程式,source codes 中有 main.c, foo.c, target.h,並且需要 /usr/local/moreFoo/lib/libpthread.so 這個共享函式,以及 /usr/local/moreFoo/include 裡面的 headers;這麼複雜的情況又該怎麼作呢?
    • gcc main.c foo.c -I /usr/local/moreFoo/include -L /usr/local/moreFoo/lib -lpthread -O3 -ansi -o main
      以上新出現的參數定義如下:
      • -I:需要 include 某些 headers 所在的目錄,通常 include 目錄都放置headers,利用-I使編譯器知道去哪裡找原始碼裡宣告的 header。gcc預設會去尋找headers的目錄大致有:
        • /usr/include
        • /usr/local/include
        • /usr/src/linux-headers-`uname -r`/include
        • /usr/lib/gcc/i486-linux-gnu/UR_GCC_VERSION/include
        • Current Dictionary
        因此,當原始碼內有宣告 #include <fakeFoo.h>,但 fakeFoo.h 並不在上述的資料夾內,就需要利用 -I 引導 gcc 找到它。至於 target.h 因為在當前目錄,因此不必額外宣告。當然,也可以利用多個 -I 來指定多個 headers 的路徑。
      • -l:表示編譯過程需要一個 library。-lpthread 代表需要一個名為 libpthread.so的函式。
      • -L:需要額外鏈結函式庫所在的目錄,有時候程式碼經常會呼叫一些函數(methods, functions 或是 subroutines),而這些函數是使用其他人預先寫好的、已經編譯成函式(例如 libpthread.so )供人使用的話,我們就不必自己從頭寫過。gcc 預設會去找函式的目錄大致有:
        • /lib
        • /usr/lib
        • /lib/modules/`uname -r`/kernel/lib
        • /usr/src/linux-headers-`uname -r`/lib
        • /usr/local/lib
        • Current Dictionary
        因此編譯時,利用-L指定目錄告訴編譯器可以該路徑下尋找 libpthread.so。因此,若使用了-l,則必須確定所使用的 library 有在預設尋找的目錄中,否則就必須利用-L來指定路徑給編譯器。當然,可以利用多個-L來指定多個 libraries路徑。
  • 靜態、共享與動態鏈結函式庫
  • 輪子不必重複發明 -- 人家寫好的方法我們可以直接拿來用。不過很多時候,這些方法可能因為某些因素,希望提供給別人使用卻又不希望公佈原始碼,這時候編譯成 libraries 是最好的選擇。
    • 靜態函式(static libraries)
    • 靜態函式其實就是將一系列 .o 檔打包起來,因此它可以直接視為一個巨大的 .o 檔。打造出一個靜態函式的方法很簡單:
      gcc operator.c -c
      ar crsv liboperator.a operator.o
      或者
      gcc -static operator.c -loperator
      兩種方法皆能產生 liboperator.a。假設這個靜態函式在 /usr/local/foo/lib/ 裡,編譯時要與靜態函式作鏈結也很容易:
      gcc main.c /usr/local/foo/lib/liboperator.a -o main
      把靜態函式當成一般的 .o 檔一起納入 binary,也可以像這樣:
      gcc main.c -L /usr/local/foo/lib -loperator -o main
      靜態函式將所有的功能全部打包在一起,因此 binary 會變得很巨大,但是執行這個程式的所有功能都已滿足,不會再有 libraries 相依性的問題。但是缺點在於當某些libraries的功能有所更新時,這個程式就必須重新編譯,無法享受到即時更新的優點。通常商業軟體以及嵌入式系統等功能異動較少的程式,會傾向使用靜態函式。
    • 共享函式(shared libraries)
    • 共享函式跟靜態函式的觀念剛好相反,程式在執行時必須能夠找到相依的函式,否則執行時會出現錯誤訊息。製作一個共享函式的方法也很簡單:
      gcc -shared operator.c -o liboperator.so
      或是先編譯出目的檔再進行鏈結:
      gcc -c operator.c
      gcc -shared operator.o -o liboperator.so
      產生出 liboperator.so。假設這個共享函式在 /usr/local/foo/lib/ 裡,使用共享函式進行鏈結也很容易:
      gcc main.c /usr/local/foo/lib/liboperator.so -o main
      也可以像這樣:
      gcc main.c -L /usr/local/foo/lib -loperator -o main
      共享函式在程式啟動時期會檢查是否存在。以一個分別鏈結了靜態函式與共享函式的 binary 而言,執行的結果大有差別。以靜態函式鏈結的 main 程式可以順利執行,但是假設系統預設尋找函式庫的路徑裡找不到 liboperator.so,以共享函式鏈結的main程式則會出現錯誤訊息:
      ./main: error while loading shared libraries: liboperator.so: cannot open shared object file: No such file or directory
      這時解決的方法有四種:
      1. 把 liboperator.so 複製或是作一個連結到 /usr/lib 裡。
      2. 修改 /etc/ld.so.conf,把 /usr/local/foo/lib 加進系統 libraries 的搜尋範圍內。
      3. 設定 LD_LIBRARY_PATH 變數,累加該路徑進來:如果你不是系統管理員,前兩個方法根本沒辦法執行。我們只好自己加到 ~/.profile 裡:
      4. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/foo/lib
      5. 改用靜態函式進行鏈結。
      共享函式經常出現在開放原始碼的 linux 世界裡,由於使用所有函式皆是共享的,因此許多程式都可以重複利用既有的功能;有新功能或是 bug 也能簡單的替換掉該函式,所有程式都可以即時享受到這樣的改變,也是最為常見的函式型態。
    • 動態函式(dynamic libraries)
    • 動態函式跟共享函式非常類似,唯一的差別在於程式執行時期並不會去檢查該函式是否存在,而是程式執行到某功能時才進行檢查。這種動態載入的技術最常用在瀏覽器或是大型程式的外掛程式,當有需要用到這個功能時才載入進來。而製作一個動態函式較為麻煩。
      gcc -c -fPIC operator.c
      gcc -shared operator.o -o liboperator.so
      其中的 -fPIC 是產生 position-independent code,也可以用 -fpic,撰寫呼叫動態函式的程式碼也需要傳入相關參數。

2012年3月1日 星期四

CGI Overview (Based on C++)

CGI (Common Gateway Interface) Overview
  • CGI 的角色定位
  • C++ Output
    • 如果我們想要利用 C++ 在畫面上呈現出"Hello World!"的字串,我們通常會用下列的程式碼:
    • 但是如果是想要在瀏覽器上呈現出"Hello World!"的頁面,我們還有更多動作需要做,這時就需要 CGI 的支援。
  • Content-Type Header
    • 每一個 CGI Script 都必須優先輸出以 MIME 形式表示的 Content-Type Header,舉例來說:text/plain 表示純文字內容輸出;text/html 表示一個 HTML 的網頁。實際上的程式碼可能是如下所示:
  • First Simple CGI Script
    • 如上所言,我們可以簡單的寫出一個 CGI Script 如下:
    • 為使它更符合標準的網頁結構,並善用 HTML 所提供的 Tag 來美化它,可將其改為如此。
    • 如此一來,瀏覽器便能正確的解析它的內容。
  • Using HTTPd Environment Variables
    • 當我們在伺服器上利用 HTTPd Server 執行 CGI Script ,HTTPd Server 會利用許多環境變數來存放資料,我們可以用 getenv() 來取用這些環境變數,以下就以取得 Client IP address 的 CGI Script 做示範。
    • 你也可以秀出當前時間或是顯示使用者的瀏覽器類型(利用 HTTP_USER_AGENT 參數)
  • Receiving Data From Form
    • 接下來介紹使用 Http "POST" 的方式進行資料傳遞,CGI Script 可透過 STDIN 取得Data,當 HTTPd 收到資料後會先放在一塊輸入緩衝區,並將資料大小紀錄在CONTENT_LENGTH (環境變數),再以 CGI 程式的 STDIN 指向此塊緩衝區,在 C++ 下最簡單的方法是透過 fread() function 取得資料。
    • 下面用一個 HTML <FORM> 來說明 POST 資料的運作過程:
    • 當使用者送出這個表單,CGI Script 將透過 STDIN 收到以下的資料。
    • value1=test1&value2=test2&value3=test3
    • CGI Script 接收資料並回覆的程式碼內容可能如下:
  • Parsing The Form Data
    • 這裡有兩個關於CGI Library的檔案,接下來我們會使用到它們內部的函式跟功能,它們提供兩個 Classes: CCGI 跟 CCGItem,可以更方便的處理資料。
    • 以下是它們的運作方式:建立一個 CCGI 跟 CCGItem,呼叫 CCGI::Load() 來讀取資料,接著呼叫 CCGI::GetItemCount() 來取得被送出的數量,然後利用 CCGI::GetItem 來取得資料,以一個指標指向已填入相對應 item name & value 的 CCGItem ,呼叫 GetName() 以及 GetValue() 進行資料取用。請參考如下的程式碼:
現在你應該對於 CGI based on C++ 有了基本的認識,接下來你可以嘗試寫更有用且具有互動性的 CGI 程式。

2011年8月30日 星期二

The difference between char* and char[]

以下面的程式片段為例:
上面程式碼編譯執行結果會印出"string", 
但若把 method(2) 那行拿掉,只留 method(1) 那行,則編譯(g++)會有警告出現,
warning:address of local variable 'str' returned

揪竟~這兩種寫法差異為何?

在這個例子中:

[1]首先就宣告變數 str 來說,
method(1) 的變數 str 是配置在 local 區的記憶體中;
method(2) 的變數 str 也是配置在 local 區的記憶體中,但其指向的地方不是,而是在 constant pool (系統中放置常數資料的地方)。

[2]再者討論 return str; 的動作差異,
method(1) 會把陣列的記憶體位置回傳,回傳一個指向 local 區的記憶體位置,這是有風險的,因為當副程式 getStr() 完成後,其中所使用的變數 str 記憶體空間會被釋放,之後利用它的運算可能會出錯,所以編譯器會以警告告知你;
method(2) 則是回傳指標值,也就是 "string" 的所在記憶體位置,而這個值是指向非 local 區,所以是安全的動作。

說明
“xxxxxx” 常數 (literal constant string) 都是放在系統的 constant pool 中,而且通常 compiler 會將 constant pool 設為 read only。
char *ptr = “xxxxxx”; 是讓 ptr 指向 constant pool 裡第一個 ‘x’ 的位置。
char str[] = “xxxxxx”; 是在 stack 中開 7 個 bytes 的空間, 並將 ‘x’ ‘x’ ‘x’ ‘x’ ‘x’ ‘x’ + ‘\0’ 七個字元從 constant pool 中複製 (load) 過來。

2011年8月23日 星期二

好文轉錄 - C語言新手十誡

作者: khoguan (Khoguan Phuann) 看板: C_and_CPP
標題: C 語言新手十誡(增修一版)
時間: Sat Sep 3 01:07:31 2005

C 語言新手十誡(The Ten Commandments for Newbie C Programmers) by Khoguan Phuann

請注意:
(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。 但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書, 並多多實作練習。
(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並解答了。
(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果, 那只是不足為恃的一時僥倖。
(4) 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則引爆核彈、毀滅地球。
   (如果你的 C 程式是用來控制核彈發射器的話)

一、你不可以使用尚未給予適當初值的變數。

錯誤例子
正確例子
二、你不可以存取超過陣列既定範圍的空間。

錯誤例子
正確例子
說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1, 也就是 C 和 C++ 的陣列元素是從第 0 個開始算起,最後一個元素的索引值是 N-1, 不是 N。 C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界, 我們要自己寫程式來確保不會越界。一旦越界,將導致無法預期的後果。

三、你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。

錯誤例子
正確例子
說明:指標變數必需先指向某個明確的東西(object),才能進行操作。

四、你不可以將字串常數賦值(assign)給 char* 變數,然後透過該變數改寫字串的內容(只能讀不能寫)。

錯誤例子
正確例子
說明:字串常數的內容是唯讀的。上面的錯誤例子,是將其內容所在的位址賦 值給字元指標 pc, 我們透過指標只可以去讀該字串常數的內容,而不應該做 寫入的動作。而正確例子,則是另外宣告一個獨立的字元陣列,它的大小我們未明文指定([]),編譯器會自動將其設為剛好可以容納後面的字串常數起始值的大小,包括字串後面隱含的 '\0' 字元,並將字串常數的內容複製到字元陣列中,因此可以自由的對該字元陣列的內容進行讀和寫。

錯誤例子(2)
正確例子(2)
五、你不可以對尚未分配所指空間的 char* 變數,進行(字串)陣列的相關操作。其他型別的指標亦然。

錯誤例子
正確例子(1)
正確例子(2)
注意:上例用 gets() 或 scanf() 來讀入字串,是不安全的。 因為這些函式不會幫我們檢查使用者所輸入的字串長度是否超過我們所分配的 buffer 空間, 很可能會發生 buffer overflow。比較安全的做法是用 fgets() 來取代。如:
六、你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值。
[感謝 gocpp 網友提供程式例子]
錯誤例子
說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時) 被消滅,因此呼叫端得到的指標所指的字串內容就失效了。
【不過,倒是可以從函式中直接傳回字串常數,賦值給呼叫端的一個 const char * 變數,它既是唯讀的(參見第四誡),同時也具有恒常的儲存期(static storage duration),其內容將一直有效。】
正確例子 [針對字串操作,C++提供了更方便安全的 string class, 能用就盡量用]

七、你不可以只做 malloc(), 而不做相應的 free(),否則會造成記憶體漏失。
但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可以再被 free(),也不可以提取(dereference)這個指標。
[C++] 你不可以只做 new, 而不做相應的 delete.

八、你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算的結果,其範圍是否會超出變數的型別。

錯誤例子(1)
正確例子(1)
說明:在目前最普遍的32位元PC作業平台上,整數常數2000000000的型別為 signed int(簡寫為 int),相加後,其結果仍為 int, 但是 signed int 放不下 4000000000, 造成算術溢位(arithmetic overflow),很可能無法將正確的值指派給 unsigned int sum,縱使 unsigned int 放得下4000000000 的數值。注意:寫成也是不對的。
例子(2):(感謝 sekya 網友提供)
說明:在將 char 型別定義為範圍從 -128 至 +127 的系統上,int 0x80 (其值等於 +128)要轉成 char 會放不下,會產生編譯器自行定義的值。 這樣的程式就不具可移植性了。

九、你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值 超過一次以上。否則,將導致未定義的行為(undefined behavior)。

錯誤例子
正確例子 你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值, 而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的, 是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。
錯誤例子
正確例子 [C++程式]
錯誤例子
正確例子

十、你不可以在macro的定義中,不為它的參數個別加上括號。

錯誤例子
正確例子
說明:如果是用 C++, 請多多利用 inline function 來取代上述的 macro, 以免除 macro 定義的種種危險性。如: macro 定義出的「偽函式」至少缺乏下列數項函式本有的能力: (1) 無法進行參數型別的檢查。 (2) 無法遞迴呼叫。 (3) 無法用 & 加在 macro name 之前,取得函式位址。 (4) 呼叫時往往不能使用具有 side effect 的引數。例如:
錯誤例子:(感謝 yaca 網友提供)
MACRO(++x) 展開來後變成 (((++x) * (++x)) - ((++x) * (++x))) 違反了第九誡。在 gcc 4.3.3 下的結果是 -24, 在 vc++ 下是 0.

後記:從「古時候」流傳下來一篇文章
"The Ten Commandments for C Programmers" (Annotated Edition) by Henry Spence
http://www.lysator.liu.se/c/ten-commandments.html
一方面它不是針對 C 的初學者,一方面它特意模仿中古英文聖經的用語,寫得文謅謅。所以我現在另外寫了這篇,希望能涵蓋最重要的觀念以及初學甚至老手最易犯的錯誤。

原作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP 看板眾多網友提供寶貴意見及程式實例。

2011年8月16日 星期二

常用 C Library 列表

常用的 C Library functions


  • <iostream.h>
  • 標準輸入/輸出函數
    • cin  - console in  讀取鍵盤輸入資料
    • cout - console out 螢幕輸出
    • endl - 輸出時跳行

  • <string.h>
  • 字串函數
    • strlen - string lenght  字串長度
    • strcpy - string copy    複製字串
    • strcmp - string compare 比較字串
    • strcat - string catenation 串接字串
    • strstr - string search (string) 傳回次字串在主字串首次出現位置指標
    • strchr - string search (char)   傳回字元在主字串首次出現位置指標

  • <stdlib.h>
  • 轉換數值函數
    • atof - 轉換為浮點數值
    • atoi - 轉換為整數值
    • atol - 轉換為長整數值

  • <ctype.h>
  • 字元測試與轉換函數
    • isprint  - 是否為可列印字元(ASCII碼 32~126)
    • isgraph  - 是否為可顯示字元(ASCII碼 33~126)
    • isalnum  - 是否為英文字母或數字字元(A~Z, a~z, 0~9)
    • isalpha  - 是否為英文字母(A~Z, a~z)
    • isdigit  - 是否為數字字元(0~9)
    • isxdigit - 是否為16進位數字字元(0~9, A~F)
    • islower  - 是否為小寫英文字母(a~z)
    • isupper  - 是否為大寫英文字母(A~Z)
    • isspace  - 是否為空白字元(ASCII碼 32)
    • ispunct  - 是否為標點符號字元
    • iscntrl  - 是否為控制字元
    • tolower  - 轉成小寫英文字母
    • toupper  - 轉成大寫英文字母

  • <math.h>
  • 數學函數
    • rand  - 亂數函數(0 ~ RAND_MAX)
    • srand - 指定亂數函數種子數
    • time  - 自午夜算起的秒數
    • sin   - 正弦函數(徑度)
    • cos   - 餘弦函數(徑度)
    • tan   - 正切函數(徑度)
    • exp   - 指數函數
    • log   - 對數 loge函數
    • log10 - 對數 log10函數
    • pow   - 冪次方
    • sqrt  - 平方根
    • ceil  - 小數進位
    • floor - 切除小數
    • fabs  - 絕對值

2011年7月6日 星期三

Use fork() and exec() to make a process in Linux C

Use fork() and exec() to make a process in Linux C.

Example -
Result:
** Execuing program: ls -l /bin/gzip **
-rwxr-xr-x 1 root root 63080 2008-12-20 02:23 /bin/gzip
child return exit code: 0

2011年6月20日 星期一

"STATIC" - C/C++ Programming

"static" : a programming tag that confuses and troubles many people, and so am I of course.

The following content and information is referenced from others' blog:

  • Static Variable (靜態變數):
  • static 放在 variable 前時,代表這個 variable 的存活時間和整個程式一樣長。也就是 life time 跟整個 Process 一樣長,而 scope 則維持不變。
    Example:
    執行結果 x=5 y=1 TestFunction 會對 x , y 配置記憶體空間並進行 initial,當 TestFunction 結束的時候,會將 y 的記憶體歸還,下次呼叫時再重新配置一次;而x被宣告成static,所以x的記憶體不會歸還,延用著上次的值。
    碎碎念1:似乎可以看成被宣告成 static 的變數,其實就是 global 的了,只是因為 x 是宣告在 TestFunction 裡,所以只能在 TestFunction 裡用。
    碎碎念2:如果把 x 宣告在外面當成 global,那 int x; 和 static int x; 是一樣的。
    碎碎念3:在 TestFunction 裡,static int x=0; 的初始化只會做一次。如果寫成 static int x; x=0; 那結果就會跟 y 一樣。

  • Static Member Variable (靜態成員變數):
  • 在 class 裡把 member variable 宣告成 static,代表他是「與 class 相關連」,而不是「與物件相關連」。他獨立配置記憶體,獨立於 class 的任何物件而存在,這個 class 產生的所有物件共用使同一個 static member variable,甚至不需要有物件也能夠被使用。
    因為 static member variable 不屬於任何物件,所以必須定義在外面。與一般成員變數不同,static member variable 並不是經由 constructor 初始化,而是在定義時被初始化。
    碎碎念1:可以看做是一個存在於某個 class 的 global variable。

  • Static Member Function (靜態成員函式):
  • 如果static member variable是private的
    如果 static member variable 是 private 的,那就沒有辦法直接取了,那該怎麼辦呢?寫一個 Get 的 member function?
    這樣不是很奇怪嗎?因為 m_nNum 在不是 private 的時候,可以不用產生物件就可以存取,但是用 Get member function 卻要產生一個物件。所以這樣的方法並不好,因此,就要把 Get member function 也宣告成static的。
    static member function 也跟 static member variable 一樣,獨立於物件存在。可以在沒有物件產生的狀況下被使用,所以他不能使用 this,也不能存取 class 裡的一般成員變數,當然也不能使用一般成員函式了。
    也就是說,static member function 只能存取 static member variable。
    那如果真的想用呢?就要用以下的方法:
    執行結果:
    A::m_nNum=0
    建立物件a1
    A::m_nNum=0
    a1.m_nNum=0
    a1.GetNum2()=10
    a1.StaticGetNum3()=20

    建立物件a2
    A::m_nNum=0
    a2.m_nNum=0
    a2.GetNum2()=10
    a2.StaticGetNum3()=100  // 把a2的m_nNum3從20設成100
    a1.StaticGetNum3()=100  // Question!?
    a1.GetNum3()=20
    值得注意的是 a1.StaticGetNum3()=100,a1 的 m_nNum3 是 100 嗎?a1 的 m_nNum3 明明就是20,而 a2 的 m_nNum3 才是100,那為什麼這裡會錯了呢?
    主要的問題是來自於 m_pThis,我們為了讓 static member function 可以存取一般成員函式和成員變數,所以使用 m_pThis 指向自己來取得一般成員函式和成員變數,而且為了讓 static member function 可以使用 m_pThis 指標,還把 m_pThis 指標宣告成 static 的。
    宣告成 static 就表示他是所有物件共用的,所以當產生 a1 物件時,m_pThis 是指向 a1 物件的,而產生 a2 物件時 m_pThis 就改指向 a2 物件了,所以就算透過 a1 物件取得 m_pThis,也都會取得 a2 物件了。
    碎碎念1:要避免這問題,只能想辦法確保 A 類別只會產生1個物件? singleton ?沒有別的辦法嗎? 有的!就是把物件自己當成參數傳進 static member function 裡。
referenced from - C++的static @ 伊卡洛斯之翼

2011年6月2日 星期四

[C++] The significance about - extern "C"

extern "C" 是C++特有的組合關鍵字,在C裡並沒有這個的組合,僅有extern這個關鍵字!

為什麼C++會需要這樣的關鍵字組呢? 原因是C++它有一個複載(overloading)的功能,也就是說同樣的函式名稱可以有多個定義只要參數簽名不同即可。
比如說C++裡可以有以下的二個宣告


bar(int i, int j);

bar(double i, double j);

這二個函式都是同樣的名字叫foo,僅參數型式不同。然而在C語言裡是不被允許的! C++是如何處理這同名的函式呢? 其實他在編譯時會偷偷的把這二個函式名變成不同的名字,舉例來說bar(int i, int j)可能會被改成_bar_int_int(每種compiler產生不太一樣),而另一個則被改成_bar_double_double。這技術稱 Mangling。

問題來了! 當我們希望C++不要偷換函式名時該怎麼辦? 於是就有了extern "C" 這個關鍵字組出現了。這個字組就是請C++不要自己又偷天換日,請它保留原名。所以當我們宣告一個函式如下時:

extern "C" bar(int i, int j);

編譯器就不會把bar變成_bar_double_double。

實際使用的注意事項:

1/ 當C++使用C的函式庫(library)時,C++不能直接套用C的header檔。因為他會把header裡的宣告給mangleing了。所以他必須使用如下:

extern "C"
{
#include "C_LIB.h" //C_LIB 是由C語言所寫的。
}

2/ 相反的,在C語言的編譯器裡若要使用由C++所寫出來的C函式庫,那麼也不能直接的使用C++的header檔。因為此header檔必然存在 extern "C" 這個關鍵字組,而這字組C語言是不認識的。所以必需要把C++的header檔裡的extern "C" { } 移除後才可以讓C編譯器使用。

  • 注意事項


  • 一組多載函數中, 只能有一個函數被指明為 "extern C", 因為符號修飾後的關係, ex:

    void abc(int);
    "extern C" void abc(char);
    "extern C" void abc(float);
    

    使用 C 方式的修飾符號是 _函數名,因此是 _abc, 所以當第2組指明 "extern C" 也是 _abc 那麼兩組 C 函數的符號就重複了,此檢查在函數的多載就可以,但是同一個 scope 中函數沒有多載化就無法檢查出,更確切的說,"extern "C" 不受 namespace or class 修飾的影響, 因此同一編譯單元中只能有一個同名函數為 "extern C", 否則會有錯誤 ,ex:

    namespace N
    {
    extern "C" void abc(){} // _abc
    
    }
    
    extern "C" void abc(){} // _abc
    


    編譯時產生相同符號 _abc 函數定義故錯誤,由於 extern "C" 不受限於 namespace 的修飾因此將產生一個有趣現象也就是宣告和定義可以在不同的 namespace 出現。語法層面上的檢查就如同一般,而當語法檢查通過後產生符號時,extern "C" 的定義其符號不帶有 namespace 的修飾;同樣的,呼叫者其符號也不帶有 namespace 的修飾,ex:


    namespace Foo
    {
    extern "C" void abc() {} //有定義, 生成符號時, 不受限於 namespace Foo 修飾故符號為 _abc
    }
    
    namespace Bar
    {
    extern "C" void abc(); //純宣告 
    }
    
    int main()
    {
    Bar::abc(); //函數呼叫, 生成符號時, 不受限於 namespace Bar 修飾故符號為 _abc, 事實上鏈結到 ::Foo::abc()
    }
    

    還有下面如此鏈結錯誤的狀況:
    由於extern "C" 沒有多載功能,不論是 void abc() 或是 void abc(int a) 都將被以 _abc 來做為函式名稱,這是極度危險的.


    1.c
    extern "C" void abc();
    int main()
    {
    abc();
    }
    

    2.c
    #include
    extern "C" void abc(int a)
    {
    printf("%d",a);
    }
    

    當 1.c 和 2.c link 時可以,因為 c 沒有將參數名稱當作函式修飾名稱的一部分,故也就沒有多載,將 "C" 拿掉 link 時就會錯誤,因為會以 c++ 的方式來編譯。

    資料來源:C++中關於extern "C"的意義

    2011年4月19日 星期二

    Call by value , Call by pointer , and Call by reference

    • Call by Value
      • 參數會在函式中建立一個獨立的物件
      • 不論在函式中如何改變這個參數的值,都不會影響呼叫端的物件
      • 在該函式結束之後,所有的臨時物件都會清除,包括 call by value 所產生的參數
      int main() { 
          int x = 5; 
          foo(x); 
      }
      
      void foo(int y) {    // 建立一個新的物件等同於傳入的物件
          y++;    //不會改變到原呼叫端的物件
      }
      
    • Call by Pointer
      • 參數不會建立物件的複本
      • 以物件的產生、呼叫的成本考慮,傳參考與傳指標是十分類似。但傳指標與傳參考最大的分別是,參考是常數,而指標是變數
      • 因此這個指標可以指向另一個位置,另一個更明顯的分別是所有的陣列傳遞都是以傳指標的方式達成
      int main() {
          int a = 5;
          int *x;
          x = &a;
          foo(x);
      }
      
      void foo(int *y) {   // 建立一個指標指向傳入物件的記憶體位置
          (*y)++;    // 修改此*y就是修改main的*x
          int b = 9;
          int *c;
          c = &b;
          y = c;    // Thinking:compare with " *y = *c " here, how about the variables in main()?
      }
      
    • Call by Reference
      • 參數不會建立物件的複本,它只會建立一個指向呼叫端物件的參考
      • 所以如果在函式中改變這個參數的內容(先決條件是這個參數未被宣告為 const),就會改變呼叫端參數的內容
      • 特別要注意的是,Call by reference的方式,一定要在初始化時就有指向的實體,而且不能改變
    int main() { 
        int x = 5; 
        foo(x);    // 不用加& 
    }
    
    void foo(int &y) {    // 建立一個參考指向傳入物件的記憶體位置
        y++;    // 修改此y就是修改main的x,無法改變y所指向的記憶體位置
    }
    

    2010年9月17日 星期五

    [C++] STL idiom Vector

    • 基本操作
    Vector的STL型式,其實就是以物件導向的方式來操作vector,以物件的方式來操作vector是比較被鼓勵的方式,以下將介紹幾個vector的基本操作。
    • 建構一個元素為空的vector物件:
    • vector<int> ivector;
      
    • 將元素放入vector中,可以使用push_back()
    • for(int i = 0; i < 5; i++) {
          ivector.push_back(i);
      }
      
    • 將元素循序取出,則可以begin()與end()方法分別傳回起始位置的iterator與結束位置的iterator
    • for(vector<int>::iterator it = ivector.begin(); it != ivector.end(); it++) {
      
          cout << *it << " ";
      }
      cout << endl;
      
    iterator是標準函式庫定義類別(Class),它是一個指標,指向iterator物件的真正位址,對它進行++的動作,表示移動至 iterator的下一個元素,對它使用*運算子(Dereferences operator),表示提取出iterator目前位址的值,如果iterator走訪至結束位置的iterator的位址,表示元素走訪完畢。

    可以使用下標運算子[ ]來存取vector的元素,但實際上要知道vector與陣列本質上是不相同的,如最上頭那樣宣告一個空的vector物件時,其容量(capacity)為0,長度(size)也為0,所以此時不能使用ivector[0]來取得第一個元素值,因為實際上ivector中還沒有任何的元素。

    當使用push_back()將元素加入vector時,vector的長度會自動增長,由於每次增長度都要配置記憶體過於沒有效率,所以vector會自動先增加足夠的容量,當元素的長度超過容量時,才會再重新配置新的容量,可以使用capacity()取得vector容量,使用size()取得元素長度。
    #include <iostream>
    #include <vector>
    using namespace std; 
    
    int main() { 
        vector<int> ivector;
     
        for(int i = 0; i < 10; i++) {
    
            ivector.push_back(i);
        }
     
        for(vector<int>::iterator it = ivector.begin(); it != ivector.end(); it++) {
     
            cout << *it << " ";
        }
        cout << endl;
     
        cout << "capacity: " << ivector.capacity() << endl
             << "size: " << ivector.size() << endl;
     
        return 0; 
    }
    執行結果
    0 1 2 3 4 5 6 7 8 9
    capacity: 16
    size: 10
    • Vector演算
    對vector進行排序、尋找、反轉等操作,可以使用標準函式庫中的泛型演算法,要使用泛型演算法必須先含入表頭檔:
    #include <algorithm>
    下面這個程式直接示範了排序、尋找、反轉等操作:
    #include <algorithm>
    #include <iostream>
    #include <vector>
    using namespace std; 
    
    int main() { 
        int iarr[] = {30, 12, 55, 31, 98, 11, 41, 80, 66, 21};
        vector<int> ivector(iarr, iarr + 10);
     
        // 排序 
        sort(ivector.begin(), ivector.end());
     
        for(vector<int>::iterator it = ivector.begin(); it != ivector.end(); it++) {
     
        cout << *it << " ";
        }
        cout << endl;
    
        cout << "輸入搜尋值:";
        int search = 0;
        cin >> search;
                                  // 搜尋
        vector<int>::iterator it = find(ivector.begin(), ivector.end(), search);
     
        if(it != ivector.end()) {
            cout << "找到搜尋值!" << endl;
        }
        else {
            cout << "找不到搜尋值!" << endl;
        }
     
        // 反轉 
        reverse(ivector.begin(), ivector.end());
     
        for(vector<int>::iterator it = ivector.begin();
            it != ivector.end();
            it++) {
     
            cout << *it << " ";
        }
        cout << endl;
     
        return 0; 
    }
    
    11 12 21 30 31 41 55 66 80 98
    輸入搜尋值:41
    找到搜尋值!
    98 80 66 55 41 31 30 21 12 11

    2010年9月14日 星期二

    [C++] 標準類型轉換:static_cast, dynamic_cast, reinterpret_cast, and const_cast

    • static_cast
      • 可用於轉換基底類別指標為衍生類別指標,也可用於傳統的資料型態轉換。
      舉例來說,在指定的動作時,如果右邊的數值型態比左邊的數值型態型態長度大時,超出可儲存範圍的部份會被自動消去,例如將浮點數指定給整數變數,則小數的部份會被自動消去,例子如下,程式會顯示3而不是3.14:
      int num = 0;
      double number = 3.14;
      num = number;
      cout << num;
      
      由於失去的精度,在編譯時編譯器會提出警訊: [Warning] converting to `int' from `double' 如果確定這個動作是您想要的,那麼您可以使用顯式型態轉換(Explicit type conversion)或稱之為強制轉型(Cast),例如:
      int num = 0; 
      double number = 3.14; 
      num = static_cast(number);
      
      在兩個整數型態相除時,您也可以進行型態轉換,將其中一個型態轉換至double型態再進行運算,下例會顯示3.3333:
      int number = 10;
      cout << static_cast<double>(number) / 3;
      
      static_cast是Standard C++新增加的轉型語法,在這之前顯式型態轉換可以使用以下的舊語法,基於向後相容,Standard C++仍支援這種舊語法,但鼓勵使用新風格的語法
      int number = 10;
      cout << (double) number/ 3;
      
    • dynamic_cast
    • 使用static_cast(甚至是傳統的C轉型方式)將基底類別指標轉換為衍生類別指標,這種轉型方式稱為強制轉型,但是在執行時期使用強制轉型有危險性,因為編譯器無法得知轉型是否正確,執行時期真正指向的物件型態是未知的,透過簡單的檢查是避免錯誤的一種方式:
      if(typeid(*base) == typeid(Derived1)) {
          Derived1 *derived1 = static_cast<derived1*>(base);
          derived1->showOne();
      }
      
      為了支援執行時期的型態轉換動作,C++提供了dynamic_cast用來將一個基底類別的指標轉型至衍生類別指標,稱之為「安全向下轉型」(Safe downcasting),它在執行時期進行型態轉換動作,首先會確定轉換目標與來源是否屬同一個類別階層,接著才真正進行轉換的動作,檢驗動作在執行時期完成,如果是一個指標,則轉換成功時傳回位址,失敗的話會傳回0,如果是參考的話,轉換失敗會丟出bad_cast例外。 可以將執行時期型態資訊(RTTI)中的showWho()函式如下修改:
      void showWho(Base *base) {
          base->foo();
          
          if(Derived1 *derived1 = dynamic_cast>derived1*<(base)) {
              derived1->showOne();
          }
          else if(Derived2 *derived2 = static_cast>derived2*<(base)) {
              derived2->showTwo();    
          } 
      }
      
      由於dynamic_cast轉換失敗的話會傳回0,因而運算的結果不可使用,必須先行對轉換結果作檢查才可以,如上例所示。 如果使用參考的話,dynamic_cast在轉換失敗之後會丟出bad_cast例外,所以您必須使用try...catch來處理例外,例如:
      #include <iostream>
      #include <typeinfo>
      using namespace std;
      
      class Base {
      public:
          virtual void foo() = 0;
      };
      
      class Derived1 : public Base {
      public:
          void foo() {
              cout << "Derived1" << endl;
          }
      
          void showOne() {
              cout << "Yes! It's Derived1." << endl;
          }
      };
      
      class Derived2 : public Base {
      public:
          void foo() {
              cout << "Derived2" << endl;
          }
       
          void showTwo() {
              cout << "Yes! It's Derived2." << endl;
          }
      }; 
      
      void showWho(Base &base) {
          try {
              Derived1 derived1 = dynamic_cast<derived1&>(base);
              derived1.showOne();
          }
          catch(bad_cast) {
              cout << "bad_cast 轉型失敗" << endl;
          }
      }
      
      int main() { 
          Derived1 derived1;
          Derived2 derived2;
      
          showWho(derived1);
          showWho(derived2);
      
          return 0;
      }
      
      執行結果(使用dynamic_cast):
      Yes! It's Derived1.
      bad_cast 轉型失敗
      執行結果(使用static_cast):
      Yes! It's Derived1.
      Yes! It's Derived1.
    • reinterpret_cast
      • 用於將一種型態的指標轉換為另一種型態的指標,例如將char*轉換為int*
      #include <iostream>
      using namespace std;
      
      int main() {
          int* i;
          char* str = "test";
      
          i = reinterpret_cast<int>(str);
          cout << i << endl;
      
          return 0;
      } 
      
      (可能)執行結果:
      134514704
    • const_cast
      • const_cast用於一些特殊場合可以覆寫變數的const屬性,利用cast後的指標就可以更改變數的內部。
      使用格式:
      const_cast<常量(const)型態(指標) / 非常量型態(指標)>( 非常量變數(指標) / 常量變數(指標) );
      
      使用範例:
      #include <iostream>
      using namespace std; 
      
      void foo(const int*);
      
      int main() {
          int var = 10;
      
          cout << var << endl;
      
          foo(&var);
      
          cout << var << endl;
      
          return 0;
      }
      
      void foo(const int* p) {
          int *v = const_cast<int *>(p);
          *v = 20;
      }
      
      執行結果:
      10
      20

    2010年9月10日 星期五

    C++:箭號(->) 、雙冒號(::)、點(.) 之各自用法說明

  • 箭號(->)與點(.)

    • Used in C++ Struct
    struct MyStruct {
    int member_a;
    };
    

    如果有變數 MyStruct s,那麼對於其中成員的取用為:
    MyStruct s;
    s.member_a = 1;

    如果是採用指標方法存取,譬如 MyStruct * ps,那麼相對的存取方式必須使用箭號:
    MyStruct * ps;
    ps->member_a = 1;
    • Used in C++ Class
    class MyClass { 
    public: 
    int ca_var; 
    int add(int a); 
    };
    

    當宣告為一般型態時 MyClass CA,就使用點(.)來存取Class中的成員:
    MyClass CA;
    CA.add(0);
    左邊為 Class變數

    當宣告為指標型態時 MyClass * CA,就使用箭號(->)來存取Class中的成員:
    MyClass * pCA;
    pCA->add(0);
    左邊為 Class指標


  • 雙冒號(::)


  • 雙冒號(::)只用在Class成員函式或Class成員變數中:

    class MyClass { 
    public: 
    int ca_var; 
    int add(int a);
    int add(int a, int b);
    };
    

    在實作這個add()這個函式時必須這樣描述:
    int MyClass::add(int a, int b) 
    { 
    return a + b; 
    }
    

    此外,雙冒號也常常使用在當前Class內部,對目前Class內部變數元素進行表示:
    int MyClass::add(int a) 
    { 
    return a + ::ca_var; 
    }
    
    用於表示目前Class的內部變量ca_var。

    2010年4月13日 星期二

    [C-Programming] 轉貼:字串取代函式 (str_replace)

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /* str_replace [字串取代] 
    * @param {char*} source 原始的文字 
    * @param {char*} find 搜尋的文字   
    * @param {char*} rep 替換的文字 
    * */
    char *str_replace(char *source, char *find,  char *rep) {
    // 搜尋文字的長度
    int find_L=strlen(find);
    // 替換文字的長度
    int rep_L=strlen(rep);
    // 結果文字的長度
    int length=strlen(source)+1;
    // 定位偏移量
    int gap=0;
    
    // 建立結果文字,並複製文字
    char *result = (char*)malloc(sizeof(char) * length);
    strcpy(result, source);
    
    // 尚未被取代的字串
    char *former=source;
    // 搜尋文字出現的起始位址指標
    char *location= strstr(former, find);
    // 漸進搜尋欲替換的文字  
    while(location != NULL) {
        // 增加定位偏移量
        gap+=(location - former);
        // 將結束符號定在搜尋到的位址上  
        result[gap] = '\0';
    
        // 計算新的長度  
        length+=(rep_L-find_L);
        // 變更記憶體空間  
        result = (char*)realloc(result, length * sizeof(char));
        // 替換的文字串接在結果後面  
        strcat(result, rep);
        // 更新定位偏移量  
        gap+=rep_L;
        // 更新尚未被取代的字串的位址
        former=location+find_L;
        // 將尚未被取代的文字串接在結果後面  
        strcat(result, former);
    
        // 搜尋文字出現的起始位址指標
        location= strstr(former, find);
        }
    return result;
    }
    
    轉貼自 Jax的工作紀錄-[C 語言] 字串取代(str_replace)一文

    2009年12月1日 星期二

    C語言編譯參數查詢方式

    以 libcurl 為例:

    libcurl的編譯參數設置參考指令: curl-config

    ‧需要哪些 flags 來編譯我的code?
    curl-config --cflags

    ‧需要哪些參數options 來編譯我的code?
    curl-config --libs

    ‧我的有SSL 支援嗎?
    curl-config --feature | grep SSL

    ‧我的libcurl版本?
    curl-config --version