動態(tài)庫和靜態(tài)庫
當(dāng)動態(tài)庫和靜態(tài)庫同時存在的時候,會優(yōu)先使用動態(tài)庫。
靜態(tài)庫
1. 制作靜態(tài)庫
g++ -c -o lib庫名.a 源文件代碼清單
-c表示只編譯,-o則是說明需要指定文件名
2. 使用靜態(tài)庫
g++ 選項 源代碼文件名清單 -l庫名 -L庫文件所在的目錄名
3. 庫文件的概念
程序在編譯時,會將庫文件的二進(jìn)制代碼鏈接到目標(biāo)程序中,這種方式稱為靜態(tài)編譯。
如果多個程序中用到了同一個靜態(tài)庫中的函數(shù),就會存在多份拷貝。
4. 靜態(tài)庫的特點
- 靜態(tài)庫的鏈接是在編譯時期完成的,執(zhí)行的時候代碼加載速度快。
- 目標(biāo)程序的可執(zhí)行文件比較大,浪費空間
- 程序的更新和發(fā)布不方便,如果某一個靜態(tài)庫更新了,所有使用它的程序都需要重新編譯
動態(tài)庫
1. 制作動態(tài)庫
g++ -fPIC -shared -o lib庫名.so 源代碼文件清單
2. 使用動態(tài)庫
g++ 選項 源代碼文件名清單 -l庫名 -L庫文件所在的目錄名
需要注意的是:運行可執(zhí)行程序的時候,需要提前設(shè)置LD_LIBRARY_PATH環(huán)境變量。
3. 動態(tài)庫的概念
程序在編譯時不會把庫的二進(jìn)制代碼鏈接到目標(biāo)程序中,而是在運行的時候才被載入。
如果多個程序中用到了同一動態(tài)庫中的函數(shù),那么在內(nèi)存中只有一份,避免了空間浪費問題。
4. 動態(tài)庫的特點
- 程序在運行的過程中,需要用到動態(tài)庫的時候才把動態(tài)庫的二進(jìn)制代碼載入內(nèi)存
- 可以實現(xiàn)進(jìn)程之間的代碼共享,因此動態(tài)庫也稱為共享庫
- 程序升級比較簡單,不需要重新編譯程序,只需要更新動態(tài)庫就行
makefile
makefile是一個編譯規(guī)則文件,用于實現(xiàn)自動化編譯
[[…/雜項/Makefile|Makefile]]中有寫
main函數(shù)的參數(shù)
main函數(shù)有三個參數(shù),分別是argc、argv和envp:
int main(int argc, char* argv[], char* envp[]){ }
- argc:存放了程序參數(shù)的個數(shù),包括程序本身
- argv:字符串?dāng)?shù)組,存放了每個參數(shù)的值,包括程序名本身
- envp:字符串?dāng)?shù)組,存放了環(huán)境變量,數(shù)組的最后一個元素是空
什么叫包括程序本身?
在Linux中,我們想要運行這個程序,就需要在終端中使用指令:
./程序名
其實這就相當(dāng)于將程序名作為一個參數(shù)傳遞給main函數(shù),因此不管什么時候,argc最小都為1,但是我們在終端輸入的時候可能還有別的情況:
./程序名 Hello World
此時這個main函數(shù)就接收了三個參數(shù),即:argc = 3,此時的argv為:
argv[0] = "./程序名"
argv[1] = "hello"
argv[2] = "world"
操作環(huán)境變量
1. 設(shè)置環(huán)境變量
使用函數(shù)setenv():(這個函數(shù)是POSIX提供的,因此只能夠在Linux系統(tǒng)中使用)
int setenv(const char* name, const char* value, int overwrite);
- name:環(huán)境變量名
- value:環(huán)境變量的值
- overwrite:這個變量的值有兩種情況:0和非0
- 0:如果環(huán)境變量不存在,則增加新的環(huán)境變量;如果環(huán)境變量已經(jīng)存在,不替換它的值
- 非0:如果環(huán)境變量不存在,則增加新的環(huán)境變量;如果環(huán)境變量已經(jīng)存在,替換它的值
- 返回值:0(成功),-1(失敗)
注意事項
此函數(shù)設(shè)置的環(huán)境變量只對本進(jìn)程有效,不會影響shell的環(huán)境變量。
也就是說,如果執(zhí)行了setenv()函數(shù)后關(guān)閉了該程序,上次的設(shè)置失效。
獲取環(huán)境變量的值
char* getenv(const char* name);
這個函數(shù)就更簡單了,好像也沒什么好說的。
但是這個函數(shù)與setenv不同,getenv()是C/C++庫提供的,在stdlib.h(cstdlib)中。
gdb常用命令
gdb(GNU symbolic debugge)是C/C++最常用的調(diào)試工具,gdb通常需要手動安裝。
1. 安裝gdb
sudo apt install gdb
2. gdb常用命令
如果希望程序可調(diào)試,編譯的時候需要添加-g(gdb的縮寫)選項,并且不能夠使用-O選項進(jìn)行優(yōu)化。
在開始調(diào)試之前,需要輸入指令:
gdb 目標(biāo)程序
命令 | 簡寫 | 命令說明 |
---|---|---|
set args | 設(shè)置程序運行的參數(shù),例如:set args 需要輸入的參數(shù) | |
break | b | 設(shè)置斷點(可以有多個),例如:b 20,表示在第20行設(shè)置斷點 |
run | r | 開始運行程序,或在程序運行結(jié)束后重新開始執(zhí)行 |
next | n | 執(zhí)行當(dāng)前行語句,如果該語句為函數(shù)調(diào)用,不會進(jìn)入函數(shù)內(nèi)部 |
step | s | 執(zhí)行當(dāng)前行語句,如果該語句為函數(shù)調(diào)用,則會進(jìn)入函數(shù)內(nèi)部(有源碼才能進(jìn)) |
print() | p | 顯示變量或者表達(dá)式的值,如果p后面是表達(dá)式,會執(zhí)行這個表達(dá)式 |
continue | c | 繼續(xù)運行程序,遇到下一個斷點停止,如果沒有遇到斷點,程序?qū)恢边\行 |
set var | 設(shè)置變量的值 | |
quit | q | 退出gdb模式 |
Linux的時間操作
UNIX操作系統(tǒng)根據(jù)計算機(jī)產(chǎn)生的年代把1970年1月1日作為UNIX的紀(jì)元時間,1970年1月1日是時間的中間點,將從1970年1月1日起經(jīng)過的秒數(shù)用一個整數(shù)存放
time_t
time_t用于表示事件類型,它是long類型的別名,在頭文件time.h中定義,用于表示1970年1月1日到0時0秒到現(xiàn)在的秒數(shù)。
time()
time函數(shù)用于獲取操作系統(tǒng)的當(dāng)前時間,需要使用頭文件time.h
它有兩種使用方法:
- 將空地址傳給time(),并將time的返回值賦值給now:
#include <time.h> time_t now = time(0);
- 將變量的地址作為參數(shù)傳遞給time():
#include <time.h> time_t now; time(&now);
tm結(jié)構(gòu)體,localtime()和mktime()
time_t是一個長整數(shù),不符合人類的使用習(xí)慣,需要轉(zhuǎn)換成tm結(jié)構(gòu)體,tm結(jié)構(gòu)體在頭文件time.h中:
struct tm{
int tm_year; // 年份:其值等于實際年份減去1970
int tm_mon; // 月份:取值區(qū)間為[0, 11]
int tm_mday; // 日期:一個月中的日期,取值區(qū)間為[1, 31]
int tm_hour; // 時:取值區(qū)間為[0, 23]
int tm_min; // 分:取值區(qū)間為[0, 59]
int tm_sec; // 秒:取值區(qū)間為[0, 59]
int tm_wday; // 星期:取值區(qū)間為[0, 6],0是星期天,6是星期六
int tm_yday; // 從每年的1月1日開始算起的天數(shù),取值區(qū)間為[0, 365]
int tm_isdst;// 夏令時標(biāo)識符(沒啥用)
}
想要將time_h轉(zhuǎn)換為tm結(jié)構(gòu)體,需要使用庫函數(shù)localtime,需要使用頭文件time.h。
需要注意的是:loacaltime()不是線程安全的(因為它使用一個靜態(tài)的結(jié)構(gòu)來存儲轉(zhuǎn)換后的本地時間,并返回指向該結(jié)構(gòu)的指針),而localtime_r()是線程安全的(它接受一個指向存儲結(jié)構(gòu)的指針作為參數(shù),并將轉(zhuǎn)換后的本地時間存儲在該結(jié)構(gòu)中,而不需要使用靜態(tài)的存儲)。
struct tm *localtime(const time_t* timep);
struct tm *localtime_r(const time_t* timep, struct tm* result);
若是要將tm結(jié)構(gòu)體轉(zhuǎn)換成time_t,就需要使用庫函數(shù)mktime,它也在time.h中:
time_t mktime(struct tm* tm);
該函數(shù)主要用于時間的計算。
gettiemofday()
該函數(shù)用于獲取1970年1月1日到現(xiàn)在的秒和當(dāng)前秒鐘已逝去的微妙數(shù),可用于程序計時,該函數(shù)在頭文件sys/time.h鐘。
int gettimeofday(struct timeva* tv, struct timezone* tz);
struct timeval{
time_t tv_sec; // seconds
susenconds tv_usec; // microseconds
};
struct timezone{ // 時區(qū)
int tz_minuteswest; // minutes west of Greenwich
int tz_dsttime; // type of DST correction
};
程序睡眠
如果需要將程序掛起一段時間,可以使用sleep()和usleep()兩個庫函數(shù),需要使用頭文件unistd.h:
unsigned int sleep(unsigned int seconds); // 單位是秒
int usleep(useconds_t usec); // 單位是微秒
目錄操作函數(shù)
1. 獲取當(dāng)前目錄函數(shù)getcwd()和get_current_dir_name()
getcwd()和get_current_dir_name(),這兩個函數(shù)都在頭文件unistd.h中:
char* getcwd(char* buf, size_t size);
char* get_current_dir_name(void);
這兩個函數(shù)功能上沒什么區(qū)別:
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
char path1[256]; // linux系統(tǒng)目錄的最大長度嘶255
getcwd(path1, 256);
cout << "path1 = " << path1 << endl;
char* path2 = get_current_dir_name();
cout << "path2 = " << path2 << endl;
free(path2); // 注意釋放內(nèi)存
}
注意事項:get_currrent_dir_name()會動態(tài)分配內(nèi)存,需要使用char*進(jìn)行接收,并且這塊內(nèi)存需要我們進(jìn)行手動釋放,并且需要注意,get_current_dir_name()中使用的是malloc進(jìn)行內(nèi)存分配,因此我們在釋放的時候也要使用free,new、delete、malloc、free不能混用!混用可能會導(dǎo)致問題。
2. 切換工作目錄chdir()、創(chuàng)建目錄mkdir()和刪除目錄rmdir()
切換工作目錄chdir()
切換工作目錄函數(shù)chdir需要包含頭文件unistd.h:
#include <unistd.h>
int chdir(const char* path);
若是返回值為0則表示切換成功,若非0則失?。夸洸淮嬖诨驔]有權(quán)限)。
創(chuàng)建目錄mkdir()
創(chuàng)建目錄的函數(shù)名就是Linux中創(chuàng)建目錄的命令名mkdir,它需要使用頭文件sys/stat.h:
#include <sys/stat.h>
int mkdir(const char* pathname, mode_t mode);
可以看到該函數(shù)有兩個參數(shù):
- pathname:目錄名
- mode:訪問權(quán)限的數(shù)字寫法,如:0755(不能省略前置的0,因為權(quán)限數(shù)字是八進(jìn)制)
返回值和chdir()一樣。
刪除目錄rmdir()
使用過rmdir()需要包含頭文件unistd.h:
int rmdir(const char* path);
path就是要刪除的目錄的路徑,返回值和chdir()也是一樣的。
獲取目錄中文件的列表
這一系列的操作都需要使用頭文件dirent.h(dir event),一共有三個步驟:
步驟一:用opendir()打開目錄
DIR* opendir(const char* pathname);
若是成功,返回目錄的地址;若是失敗,返回空地址。
步驟二:用readdir()讀取目錄
struct dirent* readdir(DIR* dirp);
若是成功過,返回struct dirent結(jié)構(gòu)體的地址;若是失敗,返回空地址。
步驟三:用closedir()關(guān)閉目錄
int closedir(DIR* dirp);
相關(guān)的數(shù)據(jù)結(jié)構(gòu)DIR
在上面的函數(shù)中,我們使用了目錄指針DIR*,每調(diào)用一次readdir(),含稅返回struct dirent的地址,存放了本次讀取到的內(nèi)容:
struct dirent{
long d_ino; // inode number索引節(jié)點號
off_t d_off; // offset to this dirent在目錄文件中的偏移
unsigned short d_reclen; // length of this d_name文件長度名
unsigned char d_type; // the type of d_name文件類型
char d_name[NAME_MAX + 1]; // file name文件名,最長255字符(因為是Linux系統(tǒng))
};
重點在d_name和d_type:
- d_name是文件名或目錄名
- d_type是文件類型,有多種取值,這里我們只關(guān)注兩種:
- 8:常規(guī)文件
- 4:目錄
Linux的系統(tǒng)錯誤
在C++程序中,如果調(diào)用了庫函數(shù),可以通過函數(shù)的返回值判斷調(diào)用是否成功。其實還有一個整型的全局變量errno,存放了函數(shù)調(diào)用過程中產(chǎn)生的錯誤代碼。
如果調(diào)用庫函數(shù)失敗,可以通過errno的值來查找原因,這也是調(diào)試程序的一個重要方法。
使用errno需要包含頭文件errno.h(或cerrno),配合strerror()和perror()兩個庫函數(shù),可以差點出錯的詳細(xì)信息。
strerror()
strerror()在頭文件string.h中聲明,用于獲取錯誤代碼對應(yīng)的詳細(xì)信息。它有兩個版本,一個線程安全,一個非線程安全:
char* strerror(int errnum); // 非線程安全
char* strerror_r(int errnum, char* buf, size_t buflen); // 線程安全
這里給出一段示例代碼:
#include <string.h>
#include <iostream>
using namespace std;
int main(){
int ii;
for(ii=0; ii<150; ii++){ // gcc 8.3.1 一共有133個錯誤代碼
cout << ii << ":" << strerror(ii) << endl;
}
}
運行這段代碼,能看到0133都是有語句輸出的,其中:==0表示程序正常運行,1133是錯誤信息==。
perror()
perror()在頭文件stdio.h中聲明,用于在控制臺顯示最近一次系統(tǒng)錯誤的詳細(xì)信息,在實際開發(fā)中,服務(wù)程序在后臺運行,通過控制臺顯示錯誤信息意義不大:
void perror(const char* s);
注意事項
1. 調(diào)用庫函數(shù)失敗不一定會設(shè)置errno
并不是全部的庫函數(shù)在調(diào)用失敗時都會設(shè)置errno的值,以man手冊為準(zhǔn)(不屬于系統(tǒng)調(diào)用的函數(shù)不會設(shè)置errno,即:操作系統(tǒng)(OS)提供的庫才會設(shè)置errno)
2. errno不能作為調(diào)用函數(shù)失敗的標(biāo)志
errno的值只有在庫函數(shù)調(diào)用發(fā)生錯誤時才會被設(shè)置,當(dāng)庫函數(shù)調(diào)用成功時,errno的值不會被修改,不會主動得置為0。
在實際開發(fā)中,判斷函數(shù)執(zhí)行是否成功還得靠函數(shù)的返回值,只有在返回值是失敗的情況下,才需要關(guān)注errno的值。
目錄和文件的更多操作
access()
access()用于判斷當(dāng)前用戶對目錄或文件的存取權(quán)限,需要包含頭文件unistd.h:
int access(const char* pathname, int mode);
- pathname:目錄或文件名
- mode:需要判斷的存取權(quán)限,在unistd.h中存在如下宏定義:
#define R_OK 4 // 判斷是否有讀權(quán)限 #define W_OK 2 // 判斷是否有寫權(quán)限 #define X_OK 1 // 判斷是否有執(zhí)行權(quán)限 #define F_OK 0 // 判斷是否存在
- 返回值:若是pathname滿足mode權(quán)限就返回0;不滿足就返回-1,并設(shè)置errno(這也說明unistd.h是Linux提供的庫)
stat()與stat結(jié)構(gòu)體
(略)
rename()
rename()函數(shù)在頭文件stdio.h中,用于重命名目錄或文件,相當(dāng)于操作系統(tǒng)的mv命令:
int rename(const char* oldpath, const char* newpath);
- oldname:原目錄或文件名
- newpath:目標(biāo)目錄或文件名
- 返回值:0(成功),-1(失敗,并設(shè)置errno)
在實際開發(fā)中,access()主要用于判斷目錄或文件是否存在。
remove()
remove()函數(shù)在頭文件stdio.h中,用于刪除目錄或文件,相當(dāng)于操作系統(tǒng)的rm命令:
#include <stdio.h>
int remove(const char* pathname);
- pathname:待刪除的目錄或文件名
- 返回值:0(成功),-1(失敗,并設(shè)置errno)
Linux中的信號
信號的基本概念
信號(signal)是軟件中斷,是進(jìn)程之間相互傳遞消息的一種方法,用于通知進(jìn)程發(fā)生了事件,但是,不能給進(jìn)程傳遞任何數(shù)據(jù)。
信號產(chǎn)生的原因有很多,在Shell中,可以用kill和killall命令發(fā)送信號:
kill -信號的類型 進(jìn)程編號
killall -信號的類型 進(jìn)程名
信號處理、
進(jìn)程對信號的處理方法有三種:
- 對該信號的處理采用系統(tǒng)的默認(rèn)操作,大部分的信號的默認(rèn)操作是終止進(jìn)程
- 設(shè)置終端的處理函數(shù),收到信號后,由該函數(shù)來處理
- 忽略某個信號,對該信號不做處理,就像未發(fā)生過一樣
主要是通過signal函數(shù)來設(shè)置對信號的處理方式,需要包含頭文件signal.h:
sighandler_t signal(int signum, sighander_t handler);
- signum:信號的編號,在Linux中默認(rèn)有64種編號(0~63,其中很大一部分屬于自定義信號,默認(rèn)是終止進(jìn)程)
- handle:信號的處理方式
- SIG_DFL:恢復(fù)參數(shù)signum信號的處理方式為默認(rèn)行為
- 一個自定義的處理很好的函數(shù),函數(shù)的形參是信號的編號
- SIG_IGN:忽略參數(shù)signum所指的信號
這里給出一段示例:
// 如果接收到信號1,就執(zhí)行func函數(shù)中的內(nèi)容
signal(1, func);
發(fā)送信號
可以使用kill庫函數(shù)發(fā)送信號:
int kill(pid_t pid, int sig);
- pid:指定的進(jìn)程
- sig:所指定的需要發(fā)送的信號
其他內(nèi)容后續(xù)再來補(bǔ)充
進(jìn)程終止
一共有八種方式可以終止進(jìn)程,其中5種為正常終止:
- main()中使用return返回
- 在任意函數(shù)中調(diào)用exit()
- 在任意函數(shù)中調(diào)用_exit()或_Exit()
- . 最后一個線程中其啟動例程(線程主函數(shù))用return返回
- 在最后一個線程中調(diào)用pthread_exit()返回
還有3種異常終止:
- 調(diào)用abort()終止
- 接收到一個信號
- 最后一個線程對取消請求做出響應(yīng)
進(jìn)程終止的狀態(tài)
在main()中,return的返回值即終止?fàn)顟B(tài),如果沒有return語句或調(diào)用exit(),那么該進(jìn)程的終止?fàn)顟B(tài)是0。
在Shell中,查看進(jìn)程的終止?fàn)顟B(tài):
echo &?
正常終止進(jìn)程的三個函數(shù):
- exit()
- _Exit()
- _exit()
其中,前兩個是ISO C說明的,_exit()是POSIX說明的:
void exit(int status);
void _exit(int status);
void _Exit(int status);
status即為進(jìn)程終止的狀態(tài)。
如果進(jìn)程不是正常終止,打印的終止?fàn)顟B(tài)為非0。
調(diào)用可執(zhí)行程序
Linuz提供了system()和exec()函數(shù)族,在C++程序中,可以執(zhí)行其他的程序(二進(jìn)制文件,操作系統(tǒng)命令或Shell腳本)
system()
system()提供了一種簡單的執(zhí)行程序的方法,需要使用頭文件stdlib.h,把需要執(zhí)行的程序和參數(shù)用一個字符串傳給system()就行了。
int system(const char* string);
system()的返回值比較麻煩:
- 如果函數(shù)執(zhí)行失敗,system()返回值非0
- 如果程序執(zhí)行成功,并且被執(zhí)行的程序終止?fàn)顟B(tài)是0,此函數(shù)的返回值即為0
注意事項
在使用此函數(shù)的時候,傳遞的參數(shù)最好使用全路徑,這樣可以避免環(huán)境變量的問題。文章來源:http://www.zghlxwxcb.cn/news/detail-809050.html
exec函數(shù)族
exec函數(shù)族提供了另一種在進(jìn)程中調(diào)用程序(可執(zhí)行文件或Shell腳本)的辦法:文章來源地址http://www.zghlxwxcb.cn/news/detail-809050.html
int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., char* const envp[]);
int execcv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execvpr(const char* file, char* const argv[], char* const envp[]);
到了這里,關(guān)于C++Linux網(wǎng)絡(luò)編程基礎(chǔ)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!