星期一, 十二月 14, 2009

Delegation in C - 在 C 語言當中實作委派方法 (2)

延續前一篇文章的想法 Delegation in C - 在 C 語言當中實作委派方法 (1)
像是
invoke_all_func(&list, "Hello World");
這樣的函式顯然不夠彈性
於是想到一個解決方法就是傳入一個 function pointer 給 invoke_all_func() 於是就變成像是下面這樣
invoke_all_func(&list, my_cb_func, "Hello World");
讓 invoke_all_func() 內部去 callback 這個傳入的 function pointer
再利用 <stdarg.h> 所提供相關的函式將傳入的參數取回
因為只有呼叫 invoke_all_func() 的地方才知道傳入的參數有哪些
所以負責將參數取來出也是相當合理的於是 my_cb_func() 的實作就會像下面這樣
static bool my_cb_func(func_t func, va_list ap)
{
    my_func_t my_func = (my_func_t) func;
    const char* value = va_arg(ap, const char*);
    return my_func(value);
}
其中的 my_func_t 是
typedef bool (*my_func_t)(const char*);
於是就可以定義不一樣的 my_func_t 跟 my_cb_func() 來使用同樣 event bubbling 的方法
達到程式碼可以重複使用的好處
詳細的程式碼實作放在 http://fd.idv.tw/tux/callback.tar.bz2
沒有版權也不負責任更不保證沒有長蟲~ :P

星期日, 十二月 13, 2009

Delegation in C - 在 C 語言當中實作委派方法 (1)

原本的想法是想要能夠在 C 語言中實現 event bubbling
提供一些函式去註冊需要被 callback 的函式
然後在 event 發生時去呼叫所有註冊過的函式
於是就有了以下的程式碼

首先來看一段程式碼片段
int main(int argc, char* argv[])
{
    list_t* list = NULL;

    add_func(&list, funcA);
    add_func(&list, funcB);
    add_func(&list, funcC);

    invoke_all_func(&list, "Hello World");

    return 0;
}
這邊利用 add_func() 將 funcA, funcB, funcC 註冊到某個 double linked list 上面
然後呼叫 invoke_all_func() 去呼叫註冊好的 funcA, funcB, funcC
static bool funcA(const char* str)
{
    fprintf(stderr, "%s says %s\n", __FUNCTION__,  str);
    return true;
}

static bool funcB(const char* str)
{
    fprintf(stderr, "%s says %s\n", __FUNCTION__,  str);
    return true;
}

static bool funcC(const char* str)
{
    fprintf(stderr, "%s says %s\n", __FUNCTION__,  str);
    return true;
}
執行後的結果是
funcC says Hello World
funcB says Hello World
funcA says Hello World
這邊的設計上是越後面註冊的函式越先被呼叫到
如果把 funcC() 的回傳值改成 false 結果就會變成
funcC says Hello World
只要某個註冊過的函式先回傳了 false 接下去的函式就不必再去執行了
於是這兩個負責註冊跟呼叫的函式的 prototype 應該就長得像是下面這樣
bool add_func(list_t** list, bool (*func)(const char*));
bool invoke_all_func(list_t** list, const char* msg);
然後再去實作這些函式的剩餘部份,不過問題來了...
當 funcA, funcB, funcC 換成了
static bool funcA(int val)
{
    fprintf(stderr, "%s count %d\n", __FUNCTION__,  val);
    return true;
}

static bool funcB(int val)
{
    fprintf(stderr, "%s count %d\n", __FUNCTION__,  val);
    return true;
}

static bool funcC(int val)
{
    fprintf(stderr, "%s count %d\n", __FUNCTION__,  val);
    return true;
}
或者是換成了
static bool funcA(const char* msg, int val)
{
    fprintf(stderr, "%s says '%s' count: %d\n", __FUNCTION__, msg, val);
    return true;
}

static bool funcB(const char* msg, int val)
{
    fprintf(stderr, "%s says '%s' count: %d\n", __FUNCTION__, msg, val);
    return true;
}  

static bool funcC(const char* msg, int val)
{
    fprintf(stderr, "%s says '%s' count: %d\n", __FUNCTION__, msg, val);
    return true;

可是 event bubbling 的方法還是一模一樣的,這時候我們應該怎麼辦呢?
有一種最不用花腦袋最快速可以解決問題的辦法就是去複製貼上程式碼,然後將參數換一換,有幾種不同的函式就重複幾次同樣的工作

待續...

星期日, 十二月 06, 2009

在 Linux 系統上面合併多個 PDF 檔案成為一個 PDF 檔案

今天臨時興起想要去複習一下 Advanced Linux Programming 這本相當不錯的電子書
於是就把網頁上面的 PDF 檔案都下載回電腦裡面
這時候突然覺得要這樣分別開啟多個 PDF 檔案實在是麻煩
於是在網路上面找了一些方法
第一個方法是利用 ImageMagick 這個套件裡面的 convert 指令
$ convert alp-toc.pdf alp-ch*.pdf alp-ap*.pdf alp-index.pdf alp.pdf
不過製作出來的 alp.pdf 品質感覺不是很好
第二個方法是用 ghostscript 的 gs 指令
$ gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=alp.pdf -dBATCH alp-toc.pdf alp-ch*.pdf alp-ap*.pdf alp-index.pdf
製作出來的 alp.pdf 品質不錯,不過花了不少時間
第三個方法是用 pdftk 這個套件當中的指令 pdftk
$ pdftk alp-toc.pdf alp-ch*.pdf alp-ap*.pdf alp-index.pdf cat output alp.pdf
這個方法比之前的方法都要來的快速~
另外有網友提到可以使用看看 pdfsam 因為有太多相依上的套件要額外安裝就沒有想要繼續試下去啦~ :P

星期三, 十二月 02, 2009

GNU Build System 執行檔案的分類 {bin,sbin,libexec,pkglib,noinst,check,EXTRA}_PROGRAMS

關於 {bin,noinst,check,EXTRA}_PROGRAMS 在之前的文章已經介紹過了
剩下來的 {sbin,libexec,pkglib}_PROGRAMS 跟 bin_PROGRAMS 其實都是在 $make install 會安裝到預設的目錄底下
差別只在於安裝到哪個目錄底下,例如以下的 Makefile.am
bin_PROGRAMS = main1
sbin_PROGRAMS = main2
libexec_PROGRAMS = main3
pkglib_PROGRAMS = main4
noinst_PROGRAMS = main5
check_PROGRAMS = main6
EXTRA_PROGRAMS = main7

main1_SOURCES = main.c
main2_SOURCES = main.c
main3_SOURCES = main.c
main4_SOURCES = main.c
main5_SOURCES = main.c
main6_SOURCES = main.c
main7_SOURCES = main.c
如果沒有特別指定或改變 prefix 最後安裝的情況會像下面這樣
usr
`-- local
    |-- bin
    |   `-- main1
    |-- lib
    |   `-- hello
    |       `-- main4
    |-- libexec
    |   `-- main3
    `-- sbin
        `-- main2
main1 這個執行檔如預期會安裝到 bin 底下
main2 類似 main1 會安裝到 sbin 底下
另外比較不常見的 libexec_PROGRAMS 則會讓 main3 安裝在 libexec 底下
還有 pkglib_PROGRAMS 會讓 main4 安裝在 lib/hello 底下,其中的 hello 就是 configure.ac 裡面的 package name
configure.ac 如下:
AC_PREREQ([2.63])
AC_INIT([hello], [0.0], [fourdollars@gmail.com])
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile])
AC_OUTPUT
當然有點經驗的 Autotools 使用者都會知道這個 configure.ac 是用 $ autoscan 產生的 configure.scan 修改而來的
剩下的 main5, main6, main7 如前一篇文章所介紹的一樣,是不會安裝進系統之中的

星期六, 十一月 28, 2009

GCC 最佳化參數對 const 變數的影響

之前有寫過幾篇關於 const 變數的文章
這次要實驗的程式碼為
/* main.c */
#include <stdio.h>

void foo(const int* const);
void bar(int);

int main(int argc, char* argv[])
{
    const int value = 11;
    printf("%d\n", value);
    foo(&value);
    bar(value);
    printf("%d\n", value);
    return 0;
}

void foo(const int* const value)
{
    int* tmp = (int*) value;
    *tmp = 22;
    printf("%d\n", *value);
}

void bar(int value)
{
    printf("%d\n", value);
}
以及
/* main.cpp */
#include <cstdio>

void foo(const int* const);
void bar(int);

using namespace std;

int main(int argc, char* argv[])
{
    const int value = 11;
    printf("%d\n", value);
    foo(&value);
    bar(value);
    printf("%d\n", value);
    return 0;
}

void foo(const int* const value)
{
    int* tmp = const_cast<int*>(value);
    *tmp = 22;
    printf("%d\n", *value);
}

void bar(int value)
{
    printf("%d\n", value);
}
為了要實驗各個參數寫了一個 Makefile
all:
    $(CC) -Wall -g -O0 main.c -o main-O0
    @echo main-O0 && ./main-O0
    $(CC) -Wall -g -O1 main.c -o main-O1
    @echo main-O1 && ./main-O1
    $(CC) -Wall -g -Os main.c -o main-Os
    @echo main-Os && ./main-Os
    $(CC) -Wall -g -O2 main.c -o main-O2
    @echo main-O2 && ./main-O2
    $(CC) -Wall -g -O3 main.c -o main-O3
    @echo main-O3 && ./main-O3
    $(CXX) -Wall -g -O0 main.cpp -o main-O0
    @echo main-O0 && ./main-O0
    $(CXX) -Wall -g -O1 main.cpp -o main-O1
    @echo main-O1 && ./main-O1
    $(CXX) -Wall -g -Os main.cpp -o main-Os
    @echo main-Os && ./main-Os
    $(CXX) -Wall -g -O2 main.cpp -o main-O2
    @echo main-O2 && ./main-O2
    $(CXX) -Wall -g -O3 main.cpp -o main-O3
    @echo main-O3 && ./main-O3
執行 make 後得到的結果為
cc -Wall -g -O0 main.c -o main-O0
main-O0
11
22
22
22
cc -Wall -g -O1 main.c -o main-O1
main-O1
11
22
11
11
cc -Wall -g -Os main.c -o main-Os
main-Os
11
22
11
11
cc -Wall -g -O2 main.c -o main-O2
main-O2
11
22
11
11
cc -Wall -g -O3 main.c -o main-O3
main-O3
11
22
11
11
g++ -Wall -g -O0 main.cpp -o main-O0
main-O0
11
22
11
11
g++ -Wall -g -O1 main.cpp -o main-O1
main-O1
11
22
11
11
g++ -Wall -g -Os main.cpp -o main-Os
main-Os
11
22
11
11
g++ -Wall -g -O2 main.cpp -o main-O2
main-O2
11
22
11
11
g++ -Wall -g -O3 main.cpp -o main-O3
main-O3
11
22
11
11
由結果可以看出 C++ 對於 const 變數的處理都一樣
反而在 C 上面對預設的 -O0 參數不會執行任何常數字面的最佳化處理

星期五, 十一月 27, 2009

GNU Build System 當中 {bin,noinst,check,EXTRA}_PROGRAMS 使用上的分別

在 Automake 的腳本檔 Makefile.am 裡面,要編寫一個執行檔案的規則可以這樣寫


bin_PROGRAMS = mp3player
mp3player_SOURCES = mp3player.c
...

這樣在 $ ./configure 之後就可以使用 $ make 編譯出 mp3player 這個執行檔
甚至於 $ make install 還可以幫你安裝到預定的目錄底下
這個預定的目錄是像 $ ./configure --prefix=/opt 這樣指定的
如果沒有特別指定的話,通常預設值都會是 /usr/local

如果只想要編譯出執行檔卻不想要在 $ make install 時,被安裝到預定的目錄底下,在腳本檔應該要這樣寫
noinst_PROGRAMS = mp3player

如果想要在 make 時,不會去編譯執行檔的話,只有在 $ make check 時才會去編譯執行檔,可以這樣寫
check_PROGRAMS = mp3player

如果想要在 $ make 跟 $ make check 都不會去編譯執行檔,只有在 $ make mp3player 時才會去編譯執行檔,可以這樣寫
EXTRA_PROGRAMS = mp3player

更詳細的說明請看 $ info automake

P.S. 有 $ 開頭代表在命令列下輸入的指令

星期一, 十一月 16, 2009

GCC C++ Compiler 根本沒有還原 const 變數的數值,只不過先行代換掉使用 const 變數的地方。

延續前三篇文章的討論
C++ Programming: Call by Reference/Value/Pointer and const_cast
GCC C++ Compiler 會聰明地幫你還原 const 變數的數值
GCC C++ Compiler 對於 const 變數的處理到底會有多聰明呢?
這次實驗的程式碼如下
#include <iostream>

void foo(const int&);
void bar(const int&);

using namespace std;

int main(int argc, char* argv[])
{
    const int value = 11;
    cout << value << endl;
    foo(value);
    cout << value << endl;
    bar(value);
    cout << value << endl;
    cout << *(int*)&value << endl;
    return 0;
}

void foo(const int& value)
{
    int& tmp = const_cast<int&>(value);
    tmp = 22;
}

void bar(const int& value)
{
    cout << value << endl;
}
正如網友 jclin 提到的「我比較不相信 GCC 會幫你還原 const value 的作法. 只是 compiler 知道那是 const, 基於 optimization 的角度
還有 chihchun 提到的「main::value 是一個 alias, compiler (g++ 4.3.4) 會替換成 integral literal,但是依然會在 Stack 中配置此變數供人使用。
GCC C++ Compiler 只不過是把原始碼先轉換成以下這段
#include <iostream>

void foo(const int&);
void bar(const int&);

using namespace std;

int main(int argc, char* argv[])
{
    const int value = 11;
    cout << 11 << endl;
    foo(value);
    cout << 11 << endl;
    bar(value);
    cout << 11 << endl;
    cout << *(int*)&value << endl;
    return 0;
}

void foo(const int& value)
{
    int& tmp = const_cast<int&>(value);
    tmp = 22;
}

void bar(const int& value)
{
    cout << value << endl;
}
const 變數並不代表它無法被改變數值,透過 const_cast 還是可以改變數值的,只不過已經先被當作字面常數替換掉的地方,在 C++ 程式碼的運作上不是那麼容易瞭解。