nm 用法與範例

本篇 ShengYu 介紹 Linux nm 用法與範例。

nm 顯示符號表 symbols

這邊示範 nm 顯示符號表 symbols,

1
nm -gC <executable binary|library>

-g
–extern-only
僅顯示外部符號。
Display only external symbols.

-C
–demangle[=style]
將低階符號名稱解碼成用戶級名稱,除了去掉所開頭的下劃線之外,還使得C++函式名稱以可理解的方式顯示出來。
Decode (demangle) low-level symbol names into user-level names. Besides removing any initial underscore prepended by the system, this makes C++ function names readable. Different compilers have different mangling styles. The optional demangling style argument can be used to choose an appropriate demangling style for your compiler.

以上就是 nm 用法與範例的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

參考
nm命令_Linux nm 命令用法详解:显示二进制目标文件的符号表
https://man.linuxde.net/nm

其它相關文章推薦
Linux 常用指令教學懶人包
objdump 用法與範例
readelf 用法與範例

readelf 用法與範例

本篇 ShengYu 介紹 Linux readelf 指令用法,並附上 Linux readelf 範例。

Linux readelf 指令用來顯示 elf 檔案格式裡的資訊,常用於顯示 symbols、headers、sections、segments,這在分析編譯器如何從原始碼生成二進制檔案時非常實用。

Linux readelf 有一些跟 objdump 指令相似的功能,但 readelf 能顯示更多細節。

readelf 顯示符號表 symbols

這邊示範 readelf 顯示符號表 symbols,

1
readelf -Ws <library>

-s
–syms
–symbols
顯示符號表(如果有的話)。
Displays the entries in symbol table section of the file, if it has one.

-W
–wide 寬行輸出。
Don’t break output lines to fit into 80 columns.

readelf 搭配 c++filt 解析 c++ 符號

用 c++filt 把 symbol name 轉換 demangle 成看得懂的 symbol name.

1
readelf -Ws <library> | c++filt

例如用 readelf 查看 libzmq.so 可以發現像 _ZNSt7__cxx1119basic_ostringstreamIcSt11char_traitsIcESaIcEED1Ev 這一串實在很難短時間內看出是什麼,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ readelf -Ws `gcc -print-file-name=libzmq.so.5`
# 或者 readelf -Ws /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libzmq.so.5

Symbol table '.dynsym' contains 198 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 000000000000bb90 0 SECTION LOCAL DEFAULT 9
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bind@GLIBC_2.2.5 (3)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_@GLIBCXX_3.4 (4)
5: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND _ZTVSt9basic_iosIcSt11char_traitsIcEE@GLIBCXX_3.4 (4)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND randombytes_close
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND if_nametoindex@GLIBC_2.2.5 (3)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND isxdigit@GLIBC_2.2.5 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _ZNSt7__cxx1119basic_ostringstreamIcSt11char_traitsIcESaIcEED1Ev@GLIBCXX_3.4.21 (5)
#...略

這時可以用 c++filt 工具轉換成看得懂的 symbol name,

1
2
$ echo _ZNSt7__cxx1119basic_ostringstreamIcSt11char_traitsIcESaIcEED1Ev | c++filt
std::__cxx11::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >::~basic_ostringstream()

下次用 readelf 查看 libzmq.so 時後面接續 c++filt,這樣就輕鬆看了~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ readelf -Ws `gcc -print-file-name=libzmq.so.5` | c++filt

Symbol table '.dynsym' contains 198 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 000000000000bb90 0 SECTION LOCAL DEFAULT 9
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bind@GLIBC_2.2.5 (3)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)@GLIBCXX_3.4 (4)
5: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND vtable for std::basic_ios<char, std::char_traits<char> >@GLIBCXX_3.4 (4)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND randombytes_close
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND if_nametoindex@GLIBC_2.2.5 (3)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND isxdigit@GLIBC_2.2.5 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND std::__cxx11::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >::~basic_ostringstream()@GLIBCXX_3.4.21 (5)
#...略

readelf 查看依賴的函式庫

有時候需要瞭解某個執行檔或共享函式庫執行時所依賴的函式庫是哪些,就可以用 readelf 或者 ldd 來查看,

1
readelf -d <executable binary|library> | grep NEEDED

例如查看 gdb 執行檔的所依賴的函式庫是哪些,這些 Shared library 顯示 NEEDED 表示執行時需要,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ readelf -d `which gdb` # 或者 readelf -d /usr/bin/gdb

Dynamic section at offset 0x629d68 contains 36 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libreadline.so.6]
0x0000000000000001 (NEEDED) Shared library: [libz.so.1]
0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]
0x0000000000000001 (NEEDED) Shared library: [libncurses.so.5]
0x0000000000000001 (NEEDED) Shared library: [libtinfo.so.5]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libpython3.5m.so.1.0]
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libexpat.so.1]
0x0000000000000001 (NEEDED) Shared library: [liblzma.so.5]
0x0000000000000001 (NEEDED) Shared library: [libbabeltrace.so.1]
0x0000000000000001 (NEEDED) Shared library: [libbabeltrace-ctf.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x45a8d8
0x000000000000000d (FINI) 0x79ea6c
#...略

例如查看 libpthread.so 函式庫的所依賴的函式庫是哪些,這些 Shared library 顯示 NEEDED 表示執行時需要,

1
2
3
4
5
6
7
8
9
10
11
$ readelf -d `gcc -print-file-name=libpthread.so.0`
# 或者 readelf -d /lib/x86_64-linux-gnu/libpthread.so.0

Dynamic section at offset 0x17d50 contains 31 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]
0x000000000000000e (SONAME) Library soname: [libpthread.so.0]
0x000000000000000c (INIT) 0x5580
0x000000000000000d (FINI) 0x12ad4
#...略

以上就是 readelf 用法與範例的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

參考
https://man7.org/linux/man-pages/man1/readelf.1.html
https://linux.die.net/man/1/readelf
readelf命令_Linux readelf 命令用法详解:用于显示elf格式文件的信息
https://man.linuxde.net/readelf

其它相關文章推薦
Linux 常用指令教學懶人包
objdump 用法與範例
nm 用法與範例
Linux ldd 查看執行檔執行時需要哪些 library

objdump 用法與範例

本篇 ShengYu 介紹 Linux objdump 用法與範例。

objdump 顯示符號表 symbols

這邊示範 objdump 顯示符號表 symbols,

1
objdump -TC xxx.so

-T
–dynamic-syms
顯示文件的動態符號表入口,僅僅對動態目標文件意義,比如某些共享庫。它顯示的資訊類似於 nm -D|–dynamic 顯示的資訊。

-C
–demangle[=style]
將低階符號名稱解碼成用戶級名稱,除了去掉所開頭的下劃線之外,還使得C++函式名稱以可理解的方式顯示出來。

objdump 反組譯

這邊示範 objdump 反組譯,

1
objdump -S xxx.so

-S –source 儘可能反彙編出原始碼,尤其當編譯時指定-g參數時,效果比較明顯。

以上就是 objdump 用法與範例的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

參考
GNU 程式設計 – Objdump 目的檔觀察工具 - 陳鍾誠的網站
http://ccckmit.wikidot.com/gnu:objdump
objdump命令_Linux objdump 命令用法详解:显示二进制文件信息
https://man.linuxde.net/objdump
老陳獨白: 二進位實用程式(objdump, readelf,ar, nm等)
http://myblog-maurice.blogspot.com/2011/12/objdump-readelfar-nm.html

其它相關文章推薦
Linux 常用指令教學懶人包
readelf 用法與範例
nm 用法與範例

printf 列印 int32_t / uint32_t 的方法

本篇紀錄如何用 printf 列印 int32_t / uint32_t 的方法。

Windows / Linux / Unix-like
int32 在 64-bit/32-bit 作業系統定義成 int (printf 列印要用 %d)
uint32 在 64-bit/32-bit 作業系統定義成 unsigned int (printf 列印要用 %u)

範例. 印出 int32_t / uint32_t

以下範例簡單示範用 printf 印出 int32_t / uint32_t。

1
2
3
4
5
int32 a = 10;
printf("%d", a);

uint32 b = 10;
printf("%u", b);

相關文章
32/64bit 作業系統 printf 列印 int64_t / uint64_t 的方法
printf 格式化輸出說明
printf 列印 size_t 的方法

程式segmentation fault後, 用dmesg和addr2line來除錯

本篇紀錄如何從程式的 segmentation fault,配合 dmesg 和 addr2line 來除錯,
查出程式是掛(死)在哪個原始碼的第幾行。

首先先來寫一個會讓程式崩潰的程式,
再來配合使用 dmesg 查 kernel log 看是掛在哪個記憶體位置,
再來配合使用 addr2line 查出該記憶體位置是在程式碼的那一行
開始吧!

寫一個會讓程式崩潰的程式

讓一個 ptr 指標指向 NULL, 使用 printf 印出 ptr 指向的值,
使用 g++ cpp-crash.cpp -o a.out 進行編譯,
之後再執行 a.out 就會發現程式執行到一半發生segmentation fault (core dumped) 了!

cpp-crash.cpp
1
2
3
4
5
6
7
8
9
10
11
12
// g++ cpp-crash.cpp -o a.out
#include <stdio.h>

void myprint(int* ptr) {
printf("%d\n", *ptr);
}

int main() {
int *ptr = NULL;
myprint(ptr);
return 0;
}

輸出

1
Segmentation fault (core dumped)

使用 dmesg 查 kernel log 看是掛在哪個記憶體位置

接著 dmesg 指令可以查看發生段錯誤的程式名稱、造成錯誤發生的記憶體地址、指令指標地址、堆疊指標地址、錯誤程式碼、錯誤原因等,
接著使用 dmesg 看看記憶體位置,找到顯示的 ip 為 0000000000400536

1
a.out[14306]: segfault at 0 ip 0000000000400536 sp 00007ffce9c58050 error 4 in a.out[400000+1000]

也可用 Android logcat 上 backtrace 的 pc 記憶體位置來去查。

配合使用 addr2line 查出該記憶體位置是在程式碼的那一行

最後使用 addr2line 找出 ip 0000000000400536 在程式碼第幾行

1
$ addr2line -Cfie ./a.out 0000000000400536

輸出結果如下

1
2
myprint(int*)
??:?

上列資訊顯示程式死在 myprint 這個函式裡,但是看不到行數,
原因是因為使用 g++ 時沒用 -g 參數讓它編譯出有除錯的資訊
使用 g++ cpp-crash.cpp -o a.out -g 再編譯一次吧!
之後在執行一次,將 ip 拿去用 addr2line 查出的結果如下,

1
2
myprint(int*)
/home/xxx/cpp-crash.cpp:5

這次就可以很清楚看到原始檔名稱與行數了!
這樣就可以回去程式碼裡好好看看是哪裡寫錯了。

使用 nm 查詢

使用 nm 指令 nm -gC a.out 可以導出 symbol,
如下所示,發現 myprint 的位置是 0000000000400526 跟上面的 ip 很接近

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0000000000601038 B __bss_start
0000000000601028 D __data_start
0000000000601028 W data_start
0000000000601030 D __dso_handle
0000000000601038 D _edata
0000000000601040 B _end
00000000004005e4 T _fini
w __gmon_start__
00000000004003c8 T _init
00000000004005f0 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
00000000004005e0 T __libc_csu_fini
0000000000400570 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
000000000040054c T main
U printf@@GLIBC_2.2.5
0000000000400430 T _start
0000000000601038 D __TMC_END__
0000000000400526 T myprint(int*)

參考
[1] 使用dmesg和addr2line查找程序崩潰後的現場報告 | cpper
http://www.cpper.cn/2016/09/09/develop/dmesg-addr2line/

[2] gdb調試core dump入門實踐(順便複習一下之前介紹過的addr2line命令調試) - stpeace的專欄
https://blog.csdn.net/stpeace/article/details/49806473

[3] 關於Segmentation fault (core dumped)幾個簡單問題的整理
https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/560380/
什麼是Core Dump,可以看看這篇的介紹

[4]【已解決】Linux下出現Segmentation Fault(core dump)錯誤 - YSBJ123的博客
https://blog.csdn.net/YSBJ123/article/details/50035169
其他也不錯的文章,這篇有好幾種讓你產生 Segmentation Fault 的程式
https://www.cnblogs.com/panfeng412/archive/2011/11/06/segmentation-fault-in-linux.html 轉載

[5] 内核 segfault 报错分析 - Jamin Zhang
https://jaminzhang.github.io/linux/Kernel-Segfault-Analysis/
解釋 error 4 代表什麼意思

其它相關文章推薦
addr2line 用法
nm 用法與範例

在 Visual Studio 的 Release 模式下使用中斷點

本篇記錄一下如何在 Visual Studio 的 Release 模式下偵錯時使用中斷點,停用最佳化。
專案 > 屬性 > C/C++ > 最佳化 > 停用
改完後可以發現 xxx.vcxproj 的 Optimization 標籤從 MaxSpeed 變成 Disabled,如下:

1
<Optimization>MaxSpeed</Optimization>

變成

1
<Optimization>Disabled</Optimization>

之後程式再重新編譯,插入中斷點,開始偵錯F5,就可以發現程式停在中斷點上了,這樣就成功搞定囉!!!

C++ std::string 用法與完整範例

本篇 ShengYu 介紹 C++ std::string 用法與範例,C++ string 是一個存放 char 的序列容器,相較於 C-Style 字串可以自由地相加字串,std::string 會負責管理記憶體的工作,大幅減輕開發者的字串操作負擔。C++ std::string 字串操作是必須要學會的基本功,我把 C++ 常用到的 std::string 用法與範例彙整在這邊,並提供完整的 std::string C++ 範例程式碼。

以下 C++ string 內容將分為這幾部份,

  • C++ string 常用功能
  • C++ string 字串初始化
  • C++ string 字串比對 / 字串比較
  • C++ string 取得子字串
  • C++ string 字串搜尋 / 字串尋找
  • C++ string 字串相加
  • C++ string 取得長度大小
  • for 迴圈尋訪 string 容器
  • 清空 string 容器
  • 判斷 string 容器是否為空
  • C++ string size() 與 capacity() 的差異
  • C++ string reserve() 預先配置容器大小的用法
  • C++ string resize() 的用法

C++ 要使用 string 容器的話,需要引入的標頭檔<string>

C++ string 常用功能

以下是 C++ std::string 內常用的成員函式,
operator=:指定字串
operator+=:添加字串
operator+:字串相加
operator==:比較兩個字串的內容是否相同
operator[]:存取索引值[i]的字元,跟陣列一樣索引值從 0 開始
at(i):存取索引值[i]的字元,跟上面 operator[] 差異是 at(i) 會作邊界檢查,存取越界會拋出一個例外
find():字串搜尋
substr():取得子字串
empty():回傳是否為空,空則回傳true
size():回傳目前長度
length():回傳目前長度

C++ string 字串初始化

C++ std::string 以下有四種常見的字串初始化方式,建構 string 一開始沒有指定初始字串的話會是一個空字串,std::string 建構子可以帶入 C-Style 字串來初始化 std::string,或者 std::string 建構子可以帶入另一個 std::string 來初始化,另外也可以在宣告 std::string 變數同時用 = 來指定初始字串,如下範例所示,

std-string.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-string.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str1; // 初始化為空字串
std::string str2("abc"); // 從 C-Style 字串來初始化 std::string
std::string str3(str2); // 從另一個 std::string 來初始化
std::string str4 = "def";

std::cout << "str1: " << str1 << '\n';
std::cout << "str2: " << str2 << '\n';
std::cout << "str3: " << str3 << '\n';
std::cout << "str4: " << str4 << '\n';
return 0;
}

結果輸出如下,

1
2
3
4
str1: 
str2: abc
str3: abc
str4: def

以下是 C-Style 字串來初始化 std::string 的幾種用法,

std-string2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// g++ std-string2.cpp -o a.out
#include <iostream>
#include <string>

int main() {
const char *p = "abcde";
std::string str1(p, 3);
std::string str2(p, 5);
std::string str3(p);
std::cout << "str1: " << str1 << '\n';
std::cout << "str2: " << str2 << '\n';
std::cout << "str3: " << str3 << '\n';

char arr[] = "ABCDE";
std::string str4(arr);
std::cout << "str4: " << str4 << '\n';
return 0;
}

結果輸出如下,

1
2
3
4
str1: abc
str2: abcde
str3: abcde
str4: ABCDE

以下是 std::string 預先初始化連續同樣字元的用法,

std-string3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-string3.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str1(5, 0); // 初始化 5 個 0
std::string str2(5, '\0'); // 初始化 5 個 '\0' 字元
std::string str3(10, 'A'); // 初始化 10 個 A 字元
std::string str4(10, 65); // 初始化 10 個 65

std::cout << "str1: " << str1 << '\n';
std::cout << "str2: " << str2 << '\n';
std::cout << "str3: " << str3 << '\n';
std::cout << "str4: " << str4 << '\n';
return 0;
}

結果輸出如下,

1
2
3
4
str1: 
str2:
str3: AAAAAAAAAA
str4: AAAAAAAAAA

C++ string 字串比對 / 字串比較

以下介紹 C++ string 字串比較的方式,直接使用 operator== 的話是比較直覺且簡單的方式,除了用 operator== 的方式以外,還可以用 std::string::compare() 來做字串比較,

std-string4.cpp
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
// g++ std-string4.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str1 = "hello";
std::string str2 = "world";
if (str1 == str2) {
std::cout << "equal\n";
} else {
std::cout << "not equal\n";
}

if (str1 == "hello") {
std::cout << "equal\n";
} else {
std::cout << "not equal\n";
}

if (str1.compare(str2) == 0) {
std::cout << "equal\n";
} else {
std::cout << "not equal\n";
}

if (str1.compare("hello") == 0) {
std::cout << "equal\n";
} else {
std::cout << "not equal\n";
}
return 0;
}

結果輸出如下,

1
2
3
4
not equal
equal
not equal
equal

C++ string 取得子字串

C++ 使用 std::string::substr() 來取得子字串,substr 第一個參數為起始位置,從0開始,第二個參數為長度,不帶入第二個參數的話會一直到結尾,另外實務上也常會用 std::string::find() 搜尋目標字串的起始位置後再搭配 std::string::substr() 來取得子字串,如下範例,

std-string5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// g++ std-string5.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str = "hello world !!";
std::cout << str.substr(6, 5) << '\n';
std::cout << str.substr(6) << '\n';

std::size_t pos = str.find("world");
std::cout << pos << '\n';
std::cout << str.substr(pos) << '\n';
return 0;
}

結果輸出如下,

1
2
3
4
world
world !!
6
world !!

C++ string 字串搜尋 / 字串尋找

C++ 使用 std::string::find() 來字串搜尋,std::string::find() 如果有搜尋到目標字串的話會回傳起始位置,沒搜尋到的話會回傳 std::string::npos,另外 std::string::find() 回傳的起始位置可以搭配 std::string::substr() 來取得子字串,如下範例,

std-string6.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ std-string6.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str = "hello world !!";
std::size_t pos1 = str.find("hello");
std::size_t pos2 = str.find("lo");
std::size_t pos3 = str.find("world");
std::cout << pos1 << '\n';
std::cout << pos2 << '\n';
std::cout << pos3 << '\n';
std::cout << str.substr(pos3) << '\n';

std::size_t pos4 = str.find("abc");
if (found != std::string::npos) {
std::cout << "found at " << found << "\n";
} else {
std::cout << "abc not found\n";
}
return 0;
}

結果輸出如下,

1
2
3
4
5
0
3
6
world !!
abc not found

想要瞭解 std::string::find() 的詳細用法可以參考這篇

C++ string 字串相加

字串相加在 C 裡是用 strcat,這邊示範一下 C++ std::string 的字串相加用法,std::string 用 operator+ 不但可以跟 std::string 相加以外,也可以跟 C-Style 字串相加,如下範例,

std-string7.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-string7.cpp -o a.out
#include <iostream>
#include <string>

int main() {
char str[] = "wrold";
std::string str2 = "hello ";
str2 = str2 + str; // 跟 C-Style 字串相加
std::cout << str2 << '\n';

std::string str3 = " !!";
str3 = str2 + str3; // 跟 std::string 字串相加
str3 = str3 + " this is " + "c++ string " + "example";
std::cout << str3 << '\n';
return 0;
}

結果輸出如下,

1
2
hello wrold
hello wrold !! this is c++ string example

除了用 operator+= 來添加字串以外,還可以用 std::string::append() 來做字串添加,如下範例,

std-string8.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// g++ std-string8.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str = "hello ";
std::string str2 = "wrold";
str += str2;
std::cout << str << '\n';

str += " !";
std::cout << str << '\n';

str.append(" this is c++ string example");
std::cout << str << '\n';
return 0;
}

結果輸出如下,

1
2
3
hello wrold
hello wrold !
hello wrold ! this is c++ string example

C++ string 取得長度大小

這邊介紹 C++ string 取得長度大小,可以使用 std::string::length() 來取得長度,也可以使用 std::string::size() 來取得大小,兩者內部實作其實都是一樣的,都可以達成同樣的目的,如下範例,

std-string9.cpp
1
2
3
4
5
6
7
8
9
10
// g++ std-string9.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str = "hello";
std::cout << "size: " << str.size() << '\n';
std::cout << "length: " << str.length() << '\n';
return 0;
}

結果輸出如下,

1
2
size: 5
length: 5

for 迴圈尋訪 string 容器

以下介紹 for 迴圈尋訪 string 容器的使用範例,使用 operator[] 來存取索引值[i]的字元,就跟 C-Style 字元陣列的操作一樣,

std-string10.cpp
1
2
3
4
5
6
7
8
9
10
11
// g++ std-string10.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str = "hello";
for (int i = 0; i < str.length(); i++) {
std::cout << str[i] << '\n';
}
return 0;
}

結果輸出如下,

1
2
3
4
5
h
e
l
l
o

如果是要使用 range-based for loop 寫法的話,因為 range-based for loop 是 C++11 中增加的新特性,所以編譯時要加上 C++11 的編譯選項,使用範例如下,

std-string11.cpp
1
2
3
4
5
6
7
8
9
10
11
// g++ std-string11.cpp -o a.out -std=c++11
#include <iostream>
#include <string>

int main() {
std::string str = "world";
for (auto &c : str) {
std::cout << c << '\n';
}
return 0;
}

結果輸出如下,

1
2
3
4
5
h
e
l
l
o

清空 string 容器

這邊介紹 C++ 清空 string 容器的用法,要清空 string 的話可以使用 std::string::clear() 或者賦值空字串,如下範例,

std-string12.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-string12.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str = "hello";
std::cout << "str: " << str << '\n';
str.clear();
std::cout << "str: " << str << '\n';

std::string str2 = "world";
std::cout << "str2: " << str2 << '\n';
str2 = "";
std::cout << "str2: " << str2 << '\n';
return 0;
}

結果輸出如下,

1
2
3
4
str: hello
str:
str2: world
str2:

判斷 string 容器是否為空

這邊介紹 C++ 如何判斷 string 容器是否為空,要判斷是否為空的話可以使用 std::string::empty() 來作判斷,如果為空字串的話則回傳 ture,反之回傳 false,如下範例,

std-string13.cpp
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
// g++ std-string13.cpp -o a.out
#include <iostream>
#include <string>

int main() {
std::string str1;
std::string str2 = "";
std::string str3 = "wrold";

if (str1.empty()) {
std::cout << "empty\n";
} else {
std::cout << "not empty\n";
}
if (str2.empty()) {
std::cout << "empty\n";
} else {
std::cout << "not empty\n";
}
if (str3.empty()) {
std::cout << "empty\n";
} else {
std::cout << "not empty\n";
}
return 0;
}

結果輸出如下,

1
2
3
empty
empty
not empty

C++ string size() 與 capacity() 的差異

string 使用 size() 是取得目前 string 裡的元素個數,而 string 使用 capacity() 是取得目前 string 裡的預先配置的空間大小,當容量(capacity)空間不夠使用時 string 就會重新申請空間,容量(capacity)會增加為原來的容量約 2 倍,各個編譯器可能不同,來看看下面範例,

std-string14.cpp
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
// g++ std-string14.cpp -o a.out
#include <iostream>
#include <string>

using namespace std;

int main() {
std::string s;
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "abcdefghij";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "abcdefghij";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "abcdefghij";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "abcdefghij";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "abcdefghij";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "abcdefghij";
s += "abcdefghij";
s += "abcdefghij";
s += "abcdefghij";
s += "abcdefghij";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";

return 0;
}

輸出結果如下,從以下的輸出可以發現在我使用的 clang 編譯器中 capacity 是以這樣的方式增長下去,

1
2
3
4
5
6
7
size=0, capacity=22
size=10, capacity=22
size=20, capacity=22
size=30, capacity=47
size=40, capacity=47
size=50, capacity=95
size=100, capacity=191

C++ string reserve() 預先配置容器大小的用法

string 使用 reserve() 是預留空間的意思,如果我們一開始就知道容器的裡要放置多少個元素的話,可以透過 reserve() 來預先配置容器大小,這樣可以減少一直配置記憶體的機會。

如下例所示,先宣告一個 string,假設我想要預先配置好 10 個大小的話可以這樣寫 string.reserve(10),這樣會預留大於或等於 10 個元素的空間,使用 capacity() 會得到 22,但裡面還沒有任何元素所以使用 size() 會得到 0,之後用 operator+= 將元素新增進去,然後我們來觀察看看 size 與 capacity 的變化,

之後將 string 新增 5 個字元進去,再次使用 capacity() 還是 22,而使用 size() 會得到 20,

1
2
3
4
5
6
string s;
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s.reserve(10);
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "AAAAA";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";

輸出如下,

1
2
3
size=0, capacity=22
size=0, capacity=22
size=5, capacity=22

那 string reserve 預留 22 個元素的空間後,之後使用超過 22 個元素 capacity 會發生什麼變化呢?

1
2
3
4
5
6
7
8
9
10
11
string s;
s.reserve(22);
s += "AAAAAAAAAA";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "AAAAAAAAAA";
s += "AAAAAAAAAA";
s += "AAAAAAAAAA";
s += "AAAAAAA";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += "A";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";

輸出如下,可以發現當 string 的元素超過預留的 22 個元素時,會將容量增長為原本 capacity 的約兩倍,

1
2
3
size=10, capacity=22
size=47, capacity=47
size=48, capacity=95

在 string 建構子帶入數量 n 會初始化 n 個元素且預設初始值為 'A' 字元(或者其他字元或其他數字,例如:0),所以使用 size() 會回傳 n,跟上述的 reserve() 用途是不一樣的,詳見下列範例,

1
2
3
4
5
6
7
8
9
10
11
string s(2, 'A');
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s += 'B'; // AAB
s += 'C'; // AABC
s += 'D'; // AABCD
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
cout << s << "\n";
for (int i = 0; i < s.size(); i++) {
cout << (int)s[i] << " ";
}
cout << "\n";

輸出如下,一開始在 string 建構子帶入的數量 2 跟 'A' 字元會初始化 2 個 'A' 字元,

1
2
3
4
size=2, capacity=22
size=5, capacity=22
AABCD
65 65 66 67 68

C++ string resize() 的用法

C++ string 使用 resize 跟 reserve 不太一樣,resize 變大時會把多的元素補 0,例如:

1
2
3
4
5
6
7
8
string s;
s.resize(5);
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
cout << s << "\n";
for (int i = 0; i < s.size(); i++) {
cout << (int)s[i] << " ";
}
cout << "\n";

輸出如下,印出來的元素都是 0,

1
2
3
size=5, capacity=22

0 0 0 0 0

resize 如果要順便指定元素初始值的話,可以將初始值帶入 resize() 的第二個引數,像這樣寫,

1
2
3
4
5
6
7
8
string s;
s.resize(5, 'A');
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
cout << s << "\n";
for (int i = 0; i < s.size(); i++) {
cout << (int)s[i] << " ";
}
cout << "\n";

輸出如下,這些新增的元素初始值都設成 'A' 字元也就是 65,

1
2
3
size=5, capacity=22
AAAAA
65 65 65 65 65

如果 resize 的大小超過 capacity 容量大小會怎麼樣呢?

1
2
3
4
5
6
7
8
9
string s = "AAAAAAAAAABBBBBBBBBBCC";
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
s.resize(30);
cout << "size=" << s.size() << ", capacity=" << s.capacity() << "\n";
cout << s << "\n";
for (int i = 0; i < s.size(); i++) {
cout << (int)s[i] << " ";
}
cout << "\n";

輸出如下,原本的 AAAAAAAAAABBBBBBBBBBCC 元素有保留以外,剩下新增的元素補 0,

1
2
3
4
size=22, capacity=22
size=30, capacity=47
AAAAAAAAAABBBBBBBBBBCC
65 65 65 65 65 65 65 65 65 65 66 66 66 66 66 66 66 66 66 66 67 67 0 0 0 0 0 0 0 0

以上就是 C++ std::string 用法與範例的介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
string - C++ Reference
https://www.cplusplus.com/reference/string/string/
std::basic_string - cppreference.com
https://en.cppreference.com/w/cpp/string/basic_string

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
std::string::find 搜尋字串
std::vector 用法與範例
std::sort 用法與範例

撰寫 cmake 的 CMakeLists.txt

本篇介紹如何撰寫 cmake 的 CMakeLists.txt,

新增 .cmake 到 CMAKE_MODULE_PATH

用來定義自己的 cmake 模組所在的路徑。如果你的工程比較複雜,有可能會自己編寫一些 cmake 模組,這些 cmake 模組是隨你的工程釋出的,為了讓 cmake 在處理 CMakeLists.txt 時找到這些模組,你需要通過 SET 指令,將自己的 cmake 模組路徑設定一下。
比如
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
這時候你就可以通過 INCLUDE 指令來呼叫自己的模組了。

用append在之前的變數裡也可以
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

例如:想要抓取最新的 FindZLIB.cmake,但目前系統使用的 cmake 不支援,可以到這裡下載最新版本。

參考
[1] Kitware/CMake: Mirror of CMake upstream repository
https://github.com/Kitware/CMake
[2] cmake中cmakelists的编写_cmake,cmakelists,c++_Usper-CSDN博客
https://blog.csdn.net/uniqueyyc/article/details/80916779
[3] cmake 学习笔记(三)_1+1=10-CSDN博客
https://blog.csdn.net/dbzhang800/article/details/6329314

cmake 改變編譯完的輸出路徑

在 CMakeLists.txt 設定一下這些變數即可改變輸出路徑

1
2
3
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

也可以不要全部都改變,只指定某個 targets 改變就好

1
2
3
4
5
6
set_target_properties( targets...
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

CMAKE_ARCHIVE_OUTPUT_DIRECTORY 是改變 xxx.lib 的輸出路徑
CMAKE_LIBRARY_OUTPUT_DIRECTORY 是改變 ??????? 的輸出路徑
CMAKE_RUNTIME_OUTPUT_DIRECTORY 是改變 xxx.dll 的輸出路徑
在 Windows 下 設定 CMAKE_RUNTIME_OUTPUT_DIRECTORY 為 build/bin 後用debug編譯完可能會輸出到 build/bin/Debug
不想要 Debug 的路徑可以直接設定 CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG 就不會有,會變成 build/bin
Release 也是,不想要 Release 的路徑可以直接設定 CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE 就不會有了。

參考
c++ - How do I make CMake output into a ‘bin’ dir? - Stack Overflow
https://stackoverflow.com/questions/6594796/how-do-i-make-cmake-output-into-a-bin-dir

相關文章
Windows 編譯 cmake-gui
CMake 專案裡 include .cmake 檔案

[Python小專案] Tornado+PyAutoGUI 多媒體控制播放的網頁

本篇記錄用 Python 搭配 Tornado 與 PyAutoGUI 寫網頁來控制多媒體播放與控制,學會這招後,就可以使用手機來遠端控制多媒體的播放了。

動機

在家習慣使用電腦追劇,也常常會用HDMI轉接大電視,但常常要播放暫停時要跑到電腦旁操作,如果這時沒有無線鍵盤滑鼠,那還有什麼其它方法呢?科技始終來自於人性,最後想到手機最常在人們的身邊,所以手機便成為控制多媒體播放的最佳方案了,接下來問題是技術的選用,要寫App呢?還是寫網頁呢?。

App的話目前有Android與iOS兩大陣營,我要兩種都要寫嗎?答案馬上出現,NO!
我需要快速且寫一次各平台都可適用的方案!

那麼網頁呢?似乎可行,而且沒什麼平台限制,但我不想裝個Apache!
於是開始找有沒有什麼輕量的網頁伺服器且要可以跑Python,
為什麼用Python呢?因為人生苦短,我用Python,
首先這功能是自己與家人使用,細節不用太講究,重點時快速實現並且使用,總不能為了實現這個功能花了三天三夜刻一個超強的程式,太不符合經濟效益了,應以最快速度實現,接著好好享受這智慧的成果。

於是找到了 django tornado flask 三種Python web framework,
最後是使用tornado,沒什麼特別原因,只因爲我先找到的範例是tornado的,哈哈!

軟體架構與技術

軟體的流程如下,從使用者從網頁按下功能按鈕送出後,tornado webserver 收到 request 後處理事件對應的程式邏輯,例如播放事件就執行 PyAutoGUI 去播放多媒體,最後回傳 request 的結果。
user input -> tornado webserver -> PyAutoGUI -> 模擬鍵盤、滑鼠 control medeia -> return status

網頁是使用 tornado webserver,
多媒體播放控制是使用 PyAutoGUI。

(TBD…)

其它相關文章推薦
如果你想學習 Python 相關技術,可以參考看看下面的文章,
Python 安裝 PyAutoGUI 模組
Python PyAutoGUI 使用教學
Python 新手入門教學懶人包
Python str 字串用法與範例
Python list 串列用法與範例
Python set 集合用法與範例
Python dict 字典用法與範例
Python tuple 元組用法與範例