在StackExchange有人問了一個問題。在實際產品中常用的資料結構跟演算法有哪些。
把這當做一個參考,有空的時候可以研究一下。
在StackExchange有人問了一個問題。在實際產品中常用的資料結構跟演算法有哪些。
把這當做一個參考,有空的時候可以研究一下。
這篇文章正好是上一篇的相反行為。為了加速某些特殊運算,有時需要呼叫C/C++的程式碼。使用Python內建的ctypes可以幫助完成這件事。
1 | #include <iostream> |
將其編譯成Shared Library / DLL。
1 | $ g++ -c -fPIC foo.cpp -o foo.o |
接著就是Python code上場了
1 | from ctypes import cdll |
Windows版跟Linux版大同小異,最大的差別在於Windows可以寫 ./libfoo
而不用寫 ./libfoo.dll
:而 Linux沒有附檔名的話會發生錯誤。
當然,swig跟Boost Python一樣可以幫忙完成這件事。
在Mint 15下測試,Python版本2.7.3。以下的操作步驟跟Python相關,有需要的話需要修改。
首先,先安裝python-dev
。
1 | $ apt-get install python-dev |
測試的Python code cal.py
1 | def mix(a, b) : |
C語言本體:
1 | #include "Python.h" |
編譯他
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使用的時候,要注意以下情況
PyRun_SimpleString ("import sys; sys.path.insert(0, '.')");
即可找到目錄下的cal.py。當然,放到Python27\Lib
也是可以。如果要讓事情變得更簡單,SWIG或是Boost.Python都是不錯的解決方案。
參考文章
同樣是在網路上看到的
The Language ⇔ Language Matrix
可以看到很多程式語言互轉的解決方案,這年頭已經不是一個語言能解決所有問題的年代了。
在網路上看到,隨手記錄起來。
– explainshell,在搜尋框輸入Linux command,接會parsing command,從Ubuntu的manpage library中找出說明文件。 GitHub找得到Source code。 類似的服務還有cdecl將C語言的宣告樣式翻譯成白話英文。以及Regexper將Regular Expression轉化成圖形表示式。
在gdb 7.0之後加入的Reverse debugging
,終於有時間可以玩玩看了。
同樣的,來個程式範例。
1 | #include <stdio.h> |
編譯它
1 | gdb -g -o test test.c |
使用gdb來玩玩看,最簡單的用法
1 | $ gdb test |
在使用之前,要先用record
記錄操作順序,才有機會還原狀態。不用的時候可以使用record stop
停止。
接著常用的step, next, stepi跟nexti都有逆向版。可以參考GDB and Reverse Debugging。
1 | (gdb) b 15 |
想要追蹤之前執行過的中斷點該怎麼辦? 使用reverse-continue
。
也可以用watchpointer來追蹤到底如何被修改的
1 | (gdb) set can-use-hw-watchpoints 0 |
更多的gdb使用教學,可以參考
volatile最早出現在C語言。之後C++,Java,C#都沿用了這個關鍵字,不過語義大不相同。
volatile一開始的用途就是用來作Memory Mapped I/O用的。看看以下程式碼
1 | static int foo; |
聰明的Compiler在做最佳化的時候,大概會變成這個樣子。
1 | void bar_optimized(void) { |
Compiler跟CPU只會保證執行上下文結果是正確的,foo在這邊沒有被改變,所以foo != 255
永遠為true
,所以就變成無窮迴圈了。如果外界改變了foo的值,整個迴圈也有可能不會結束。 因此volatile因此而生。
自從Multithread跟Multiore programming逐漸風行之後,大家都想找到一個比lok跟輕量化的解決方案。因此valatile又被考慮了。
至於這方面晚點在說,先來說Java的volatile。
Java由於是跑在Virtual Machine上,所以所設計理念必須超越特定硬體規格。 Java Memory Model定義,一個Thread讀取一個變數時,會存在Thread的Local Spaee上運作,等到運算過後,找到是當實機再將值寫為全域空間。因此如果兩個Thread對同一個變數操作的話,可能有不可預期的結果。
以下這段Code哪裡有問題?
1 | public class RedPimple extends Thread { |
上面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 | class Foo { |
其中的helper = new Helper()
其實是以下述句組成。
1 | Allocate memory to tmp // B |
在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,因此可以保證上面的B
和C
一定會在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 | #include <stdatomic.h> |
C++11的寫法,可以參考Atomic operations library
1 | #include <atomic> |
看起來峎像,只是C++11的寫法是使用template來表示。不過更具彈性,如果你有一個HugeObject
的類別,你可以透過atomi<HugeObject>
來達成Atomic operation,裡面用個Mutex來保護。
至於C11跟C++11的Memory Model從Java那邊得到啟發而修改,那就是另外一個大故事了。有空在寫。
雖然在C語言的時候就有auto關鍵字了,不過現在已經很少使用。
C++11重新給予auto新的語意。可以在編譯的時候進行型別推導,因此你可以這樣寫
1 | int i = 0; |
不過用在這裡太大材小用了,可能簡單的東西變得更難看懂。通常都是這麼使用
1 | vector<int> vec; |
使用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 | class Person { |
GetPersonType
會編譯不過,因為外界不知道PersonType是什麼,必須改成這樣
1 | Person::PersonType Person::GetPersonType() { |
當然,改成這樣就不用這麼麻煩了
1 | auto Person::GetPersonType() -> PersonType { |
有了decltype之後,城市就能這樣寫了
1 | template <typename Builder> |
新的Return Value Syntax也應用在Lambda Expression上,有機會再說吧。
decltype跟auto算是一體的兩面,auto是可以用來宣告指定類型的變數,而decltype是得到某個變數的類別。
1 | int i = 0; |
auto裡面還是有些陷阱,對reference
跟pointer
的行為不同。
看看以下的code
1 | tatic int foov = 0; |
執行結果如下
1 | foo's value = 0 |
可以看到auto在對reference
的時候預設是Value Type,必須手動加上&
才能拿到Reference。而Pointer就一視同仁。
看起來還真不協調 _。
實際跑過之後,覺得這款軟體真的很不錯。在Coding的時候幫了很多忙。