0%

在Mint 15下測試,Python版本2.7.3。以下的操作步驟跟Python相關,有需要的話需要修改。
首先,先安裝python-dev

1
$ apt-get install python-dev

測試的Python code cal.py

1
2
3
4
def mix(a, b) :  
r1 = a + b
r2 = a - b
return (r1, r2)

C語言本體:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "Python.h"
#include <string>
using namespace std;

int main(int argc, char * argv[])
{
string filename = "cal"; // cal.py
string methodname_mix = "mix"; // function name

Py_Initialize();
PyRun_SimpleString ("import sys; sys.path.insert(0, '.')");

// load the module
PyObject * pyFileName = PyString_FromString(filename.c_str());
PyObject * pyMod = PyImport_Import(pyFileName);

// load the function
PyObject * pyFunc_mix = PyObject_GetAttrString(pyMod, methodname_mix.c_str());

// test the function is callable
if (pyFunc_mix && PyCallable_Check(pyFunc_mix))
{
PyObject * pyParams = PyTuple_New(2);
PyTuple_SetItem(pyParams, 0, Py_BuildValue("i", 5));
PyTuple_SetItem(pyParams, 1, Py_BuildValue("i", 2));

// ok, call the function
int r1 = 0, r2 = 0;
PyObject * pyValue = PyObject_CallObject(pyFunc_mix, pyParams);
PyArg_ParseTuple(pyValue, "i|i", &r1, &r2);
if (pyValue)
{
printf("%d,%d\n", r1, r2); //output is 7,3
}
}

// Clean up
Py_DECREF(pyMod);
Py_DECREF(pyFileName);

Py_Finalize();

return 0;
}

編譯他

1
$ g++ pythontest.cpp -o pythontest -I/usr/include/python2.7 -lpython2.7

其中值得注意的是PyRun_SimpleString ("import sys; sys.path.insert(0, '.')");這行。這樣的話他才會在目前目錄下找cal.py這個檔案,不然會尋找/usr/lib/python2.7這個目錄。找不到會Segmentation fault。
cal.py會先被編譯成cal.pyc,之後只需要這檔案即可。

而在Visual Studio使用的時候,要注意以下情況

  • 假設你是用x86版的Python,只能使用Win32版的Setting,不然就只能使用x64版。
  • 在Windows下需要PyRun_SimpleString ("import sys; sys.path.insert(0, '.')");即可找到目錄下的cal.py。當然,放到Python27\Lib也是可以。

如果要讓事情變得更簡單,SWIG或是Boost.Python都是不錯的解決方案。

參考文章

在網路上看到,隨手記錄起來。

explainshell,在搜尋框輸入Linux command,接會parsing command,從Ubuntu的manpage library中找出說明文件。 GitHub找得到Source code。 類似的服務還有cdecl將C語言的宣告樣式翻譯成白話英文。以及Regexper將Regular Expression轉化成圖形表示式。

在gdb 7.0之後加入的Reverse debugging,終於有時間可以玩玩看了。
同樣的,來個程式範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void foo() {
printf("inside foo()");
int x = 6;
x += 2;
}

int main() {
int x = 0;
x = x + 2;
foo();
printf("x = %d\n", x);
x = 4;
return 0;
}

編譯它

1
gdb -g -o test test.c

使用gdb來玩玩看,最簡單的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gdb test
(gdb) start
Temporary breakpoint 1, main () at test.c:10
10 int x = 0;
(gdb) record
(gdb) next
11 x = x + 2;
(gdb) disp x
1: x = 0
(gdb) next
12 foo();
1: x = 2
(gdb) reverse-next
11 x = x + 2;
1: x = 0

在使用之前,要先用record記錄操作順序,才有機會還原狀態。不用的時候可以使用record stop停止。
接著常用的step, next, stepi跟nexti都有逆向版。可以參考GDB and Reverse Debugging

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(gdb) b 15
Breakpoint 2 at 0x400578: file test.c, line 15.
(gdb) continue
Continuing.
inside foo()x = 2

Breakpoint 2, main () at test.c:15
15 return 0;
(gdb) b foo
Breakpoint 3 at 0x400524: file test.c, line 4.
(gdb) reverse-continue
Continuing.

Breakpoint 3, foo () at test.c:4
4 printf("inside foo()");

想要追蹤之前執行過的中斷點該怎麼辦? 使用reverse-continue
也可以用watchpointer來追蹤到底如何被修改的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) set can-use-hw-watchpoints 0
(gdb) watch x
Watchpoint 3: x
(gdb) continue
Continuing.
Watchpoint 3: x

Old value = 0
New value = 2
(gdb) reverse-continue
Continuing.
Watchpoint 3: x

Old value = 2
New value = 0
main () at test.c:11
11 x = x + 2;

更多的gdb使用教學,可以參考

volatile最早出現在C語言。之後C++,Java,C#都沿用了這個關鍵字,不過語義大不相同。

C / C++

volatile一開始的用途就是用來作Memory Mapped I/O用的。看看以下程式碼

1
2
3
4
5
static int foo;
void bar(void) {
foo = 0;
while (foo != 255) ;
}

聰明的Compiler在做最佳化的時候,大概會變成這個樣子。

1
2
3
4
void bar_optimized(void) {
foo = 0;
while (true) ;
}

Compiler跟CPU只會保證執行上下文結果是正確的,foo在這邊沒有被改變,所以foo != 255永遠為true,所以就變成無窮迴圈了。如果外界改變了foo的值,整個迴圈也有可能不會結束。 因此volatile因此而生。
自從Multithread跟Multiore programming逐漸風行之後,大家都想找到一個比lok跟輕量化的解決方案。因此valatile又被考慮了。
至於這方面晚點在說,先來說Java的volatile。

Java / C Sharp

Java由於是跑在Virtual Machine上,所以所設計理念必須超越特定硬體規格。 Java Memory Model定義,一個Thread讀取一個變數時,會存在Thread的Local Spaee上運作,等到運算過後,找到是當實機再將值寫為全域空間。因此如果兩個Thread對同一個變數操作的話,可能有不可預期的結果。

以下這段Code哪裡有問題?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RedPimple extends Thread {
private boolean done;
private int value;
@Override
public void run() {
while (!done) //A
Thread.yield();
System.out.println(value); // D
}
public void done() {
done = true;
}
public void setValue(int value) {
this.value = value;
}
public static void main(String[] args) {
RedPimple r = new RedPimple();
r.start();
r.setValue(1); // B
r.done(); // C
}
}

上面Code有兩個Thread,Main Thread跟r。在Main Thread那邊進行value跟done的修改,不一定會被r thread所看到。因此可能會無法結束,或是value為0的情況發生。不過這樣子在寫MultiThread的時候還是有所欠缺。拿最多人討論的https://www.youtube.com/watch?v=1j_mpwKmlJg來說,因此改變了JDK5之嘔的Memory Model,也跟著啟發了C/C++11的Memory Model。
為什麼以下這段Code是錯的

1
2
3
4
5
6
7
8
9
10
11
12
class Foo { 
private Helper helper = null;
public Helper getHelper() {
if (helper == null) // A
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}

其中的helper = new Helper()其實是以下述句組成。

1
2
3
Allocate memory to tmp  // B
Invoke Helper constructor from tmp // C
Assin helper to tmp; // D

在JDK5之前,沒有任何保證以上的述句會按照想像的順序執行,就算編譯器繞路避掉,CPU也有本事做同樣的事,可能C的執行順序在D之後。當一個Thread先執行D,卻捱沒初始化完成,另一個Thread剛好跑到’A’的點,拿到一個可能有問題的物件。就出事了。
因此語言創造者提出了Happens-Before Relation Model。主要的兩點是,每個語言的作法不盡相同,不過大都依循Acquire and Release Semantics來發展。 Java的volatile就擁有Acquire and Release Semantics。而兩者的定義如下:

A read-acquire executes before all reads and writes the same thread that follow it in program order
A write-release executes after all reads and writes by the same thread that precede it in program order

也就是兩種特殊的Memory Fence,看比較容易理解。

依照Program Order來看,在Read-acquire Fence後的讀寫不允許在Fence之前執行,不過在此之前的讀寫允許放到Fence之後進行。
依照Program Order來看,在Write-acquire Fence後的讀寫不允許在Fence之後執行,不過在此之後嘔讀寫允許放到Fence之前進行。

Java修改了Memory model,因此可以保證上面的BC一定會在D之前執行完畢。而在另一個Thread執行到A時,所拿到的helper是正確初始化的。

Visual C++從2005之後對volatile加上了Acquire and Release Semantics,不過這編有個VC失敗的範例,未來可能會修正吧。

Visual C++對volatile做了語義上的拓展,不過GCC跟Clang沒有,所以你不能保證任哦東西。
至於C11跟C++11,則是另起爐灶,用之前介紹的Atomic funtions來做,可以參考這篇

C#的用法就跟Java差不多了,有興趣的襪可以參考Volatile keyword in C# – memory model explained

寫道這邊已經語無倫次了,搞懂這些還真是複雜。

Referene:

在關注C++11的食嘔,發現C11標準也在2011年同時發佈。
目前C11的支持肚兜不太夠,因此我是用Pelles C測試。
C11的寫法,相關的資料可以參考[Threads in the new ISO C Standard from 2011](Threads in the new ISO C Standard from 2011)。

1
2
3
4
5
6
7
#include <stdatomic.h>
int main(int argc, char *argv[])
{
_Atomic(int) i = ATOMIC_VAR_INIT(0); // atomic_init(&i, 0);
atomic_fetch_add_explicit(&i, 1, memory_order_relaxed);
return 0;
}

C++11的寫法,可以參考Atomic operations library

1
2
3
4
5
6
7
8
#include <atomic>
std::atomic<int> sharedValue(0); // std::atomic_init(&sharedValue, 0);
// std::atomic<int> sharedValue = ATOMIC_VAR_INIT(0); 跟上面效果一樣
int main()
{
sharedValue.fetch_add(1, memory_order_relaxed);
return 0;
}

看起來峎像,只是C++11的寫法是使用template來表示。不過更具彈性,如果你有一個HugeObject的類別,你可以透過atomi<HugeObject>來達成Atomic operation,裡面用個Mutex來保護。

至於C11跟C++11的Memory Model從Java那邊得到啟發而修改,那就是另外一個大故事了。有空在寫。

auto

雖然在C語言的時候就有auto關鍵字了,不過現在已經很少使用。
C++11重新給予auto新的語意。可以在編譯的時候進行型別推導,因此你可以這樣寫

1
2
int i = 0;
auto j = 0;

不過用在這裡太大材小用了,可能簡單的東西變得更難看懂。通常都是這麼使用

1
2
3
vector<int> vec;
vector<int>::iterator it1 = vec.begin();
auto it2 = vec.begin();

使用auot的寫法更容易看懂,當vec的型別要從int改成double時,it1的型別需要修改,而it2則是編譯器幫我們推導,少了可能的錯誤。
C++11又新增了一種Reutrn Value Syntax
原本的函數寫成

1
int add2(int a, int b);

現在可以寫成

1
uto add2(int a, int b) -> int;

看了別人的分析之後,我才發現這樣寫的好處。
假設我們現在有個類別Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
public:
enum PersonType {
Child,
Adult,
Senior
};
void SetPersonType(PersonType t);
PersonType GetPersonType();
private:
PersonType type_;
};
void Person::SetPersonType(PersonType t) {
type_ = t;
}
PersonType Person::GetPersonType() {
return type_;
}

GetPersonType會編譯不過,因為外界不知道PersonType是什麼,必須改成這樣

1
2
3
Person::PersonType Person::GetPersonType() {
return type_;
}

當然,改成這樣就不用這麼麻煩了

1
2
3
auto Person::GetPersonType() -> PersonType {
return type_;
}

有了decltype之後,城市就能這樣寫了

1
2
3
4
5
6
7
8
template <typename Builder>
auto
makeAndProcessObject (const Builder& builder) -> decltype( builder.makeObject() )
{
auto val = builder.makeObject();
// do stuff with val
return val;
}

新的Return Value Syntax也應用在Lambda Expression上,有機會再說吧。

decltype

decltype跟auto算是一體的兩面,auto是可以用來宣告指定類型的變數,而decltype是得到某個變數的類別。

1
2
int i = 0;
decltype(i) j = i; // auto j = i;

auto兩三事

auto裡面還是有些陷阱,對referencepointer的行為不同。
看看以下的code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
tatic int foov = 0;
static int barv = 1000;
void showfoo()
{
cout << "foo's value = " << foov << endl;
}
void showbar()
{
cout << "bar's value = " << barv << endl;
}
int& foo()
{
return foov;
}
int* bar()
{
return &barv;
}
int main()
{
showfoo();
auto v1 = foo();
v1 = 1;
showfoo();
auto& v2 = foo();
v2 = 2;
showfoo();
showbar();
auto p1 = bar();
*p1 = 1001;
showbar();
auto *p2 = bar();
*p2 = 1002;
showbar();
}

執行結果如下

1
2
3
4
5
6
foo's value = 0
foo's value = 0
foo's value = 2
bar's value = 1000
bar's value = 1001
bar's value = 1002

可以看到auto在對reference的時候預設是Value Type,必須手動加上&才能拿到Reference。而Pointer就一視同仁。
看起來還真不協調 _

在網路上看到這篇,做一下自己的筆記

替代,新增指定內容的字串到文件

1
$ echo "Line 1" > file

新增字串到檔案最尾端

1
$ echo "Line 2" >> file

清空檔案內容

將檔案內容全部清空,檔案大小歸零,以下兩種作法效果是一樣的。不過第二種比較容易看懂。

1
2
$ > file
$ cat /dev/null > file

讀取一行資料

可以用以下方式讀取出來

1
$ read -r line < file

這個方式會將頭尾的空白去掉,且保留字串中的反斜線(-r)。
如果要設定IFS的話,可以順便切割赤串。

1
2
$ cat "1, 2, 3" > file
$ IFS=', ' read -r first second third < file

Read前面的IFS只影響當前的read指令,執行完之後恢復原狀。
原作中所說的

1
2
3
4
$ echo "              Emptry                        End" > file
$ IFS= read -r line < file
$ echo ${line}
Emptry End

保留空白一點效果都沒有,只好寫成這個樣子

1
2
3
4
5
6
$ SAVEIFS=$IFS
$ IFS=
$ read -r line < ttt
$ echo ${line}
Emptry End
$ IFS=$SAVEIFS

另外也可以用這種寫法

1
2
$ line=${head -1 file}
$ line=`head -1 file`

也可以這樣寫

1
2
$ IFS= line=${head -1 file}
$ IFS= line=`head -1 file`

這邊的IFS就正常了,應該是Bash的Bug吧@@。

讀取文件的每一行

1
2
3
$ while IFS= read -r line; do
# do something with ${line}}
done < file

另一種寫法,利用pipe來做

1
2
3
$ cat file | while IFS= read -r line; do
# do something with ${line}}
done

隨機讀取檔案的任合一行

同樣的,達成這樣功能也是有很多作法,例如使用Process substitution建立一個匿名pipi。

1
2
$ read -r random_line < <(shuf file)
$ read -r random_line < <(sort -R file)

上面的shuf是用來此亂檔案中美一行的排列順序,而取打亂檔案的第一行。不然可以用sort -R代替。
同樣的也可以使用Pipe來達成工作。

1
$ random_line=$(sort -R abcd | head -1)

讀取前三個字串

假設目前檔案內容是aaa bbb ccc ddd eee。我們可以用

1
$ read -r var1 var2 var3 throwaway < file

如果不在var3後面加個變數的話,var3的內容救變成ccc ddd eee了。
如果你根本不關心throwaway內容的話,可以寫成

1
$ read -r var1 var2 var3 _ < file

如果檔案內容是aaa bbb的話,var3的內容就是空的。

讀取一個文件檔有幾行,幾個單字,字元數目

同樣的,以上面aaa bbb ccc ddd eee為範例。基本上就是上面的技巧,加上wc的輸出結果。wc的用法google一下即可。

1
$ read -r lines words chars _ < <(wc abcd)

直接從字串中讀取資料

假設我們現在有個字串$info叫做`20 packets in 10 seconds”,如何取出packets跟time。
使用awk的作法會是

1
2
$ packets=$(echo $info | awk '{ print $1 }')
$ time=$(echo $info | awk '{ print $4 }')

透過Here strings跟read指令可以寫的更輕鬆

1
$ read packets _ _ time _ <<< $info

獲得檔案的大小

一樣是wc的用法,只不過現在只需要char count。

1
$ size=$(wc -c < file)

從文件路徑獲得文件名稱根目錄資訊

看起來很襖,不過實際上使用有其侷限。假設我們現在的路徑名稱是/path/to/file.ext,接著我們可以透過parameter expansion來擷取。

1
2
$ dirname=${path%/*}
$ filename=${path##*/}

不過檔案路徑改成非path開頭的,filename的定義也要跟著修改。

快速拷貝/搬移文件

透過Brace expansion,可以方便我們做這件事。

1
2
$ cp /path/to/file{, _copy}  // cp path/to/file path/to/file_copy
$ mv ~/work{, _backup}

每次更新的時候,都會塞了一堆重複的Image/Headers/Modules在系統裡面。如何移除他們

Ubuntu

請參考這邊作法

1
$ dpkg -l 'linux-*' | sed '/^ii/!d;/'"$(uname -r | sed "s/\(.*\)-\([^0-9]\+\)/\1/")"'/d;s/^[^ ]* [^ ]* \([^ ]*\).*/\1/;/[0-9]/!d' | xargs sudo apt-get -y purge

CentOS

我是參考這篇作法

1
2
3
4
5
## Install yum utils ##
$ yum install yum-utils

## Package-cleanup set count as how many old kernels you want left ##
$ package-cleanup --oldkernels --count=2

Linux的指令真的非我強項啊。看的眼花蓮亂,有時間在研究。