管道相關函數
1 pipe
- 是 Unix/Linux 系統中的一個系統調用,用于創建一個匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);
參數說明:
pipefd[2]:一個包含兩個整數的數組,用于存儲管道的文件描述符:
pipefd[0]:管道的讀端(用于從管道讀取數據)巧記:用嘴巴口型(o)讀
pipefd[1]:管道的寫端(用于向管道寫入數據)巧記:用筆(1)寫
返回值:
成功時返回 0
失敗時返回 -1 并設置 errno
2 error
errno
?是 C 和 C++ 中用于報告錯誤的全局變量(或宏),全稱為 "error number"。它由系統或標準庫函數在操作失敗時設置,用于指示具體的錯誤原因。代碼出錯時我們更想知道出錯原因,就可以用error
常見?errno
?錯誤碼
錯誤碼宏 | 值 | 含義 |
---|---|---|
EPERM | 1 | 操作無權限 |
ENOENT | 2 | 文件或目錄不存在 |
EINTR | 4 | 系統調用被中斷 |
EIO | 5 | 輸入/輸出錯誤 |
EBADF | 9 | 錯誤的文件描述符 |
EAGAIN | 11 | 資源暫時不可用 |
ENOMEM | 12 | 內存不足 |
EACCES | 13 | 權限不足 |
EFAULT | 14 | 非法內存訪問 |
EEXIST | 17 | 文件已存在 |
EDOM | 33 | 數學參數超出定義域 |
ERANGE | 34 | 結果超出范圍 |
一般和和strerror配合一起使用?
#include <iostream>
#include <cerrno>
#include <cstring>int main() {errno = 0; // 先重置 errnodouble x = sqrt(-1.0); // 嘗試計算負數的平方根if (errno == EDOM) { // EDOM 是域錯誤宏std::cerr << "Error: " << std::strerror(errno) << "\n";}
}
輸出:
Error: Numerical argument out of domain
3 strerror?
- ?是 C 標準庫中的一個函數,用于將錯誤代碼(errno 值)轉換為可讀的錯誤描述字符串。下面我會詳細解釋它的用法和實際應用場景。
#include <string.h>
char *strerror(int errnum);
參數說明:
errnum:錯誤編號(通常是 errno 的值)
返回值:
返回指向錯誤描述字符串的指針(靜態分配的字符串,不可修改)
不會失敗(永遠返回有效指針)
4 推薦使用?#include <cerrno>
?而不是?#include <errno.h>
1.?符合 C++ 標準庫的命名規范
C++ 標準庫對 C 標準庫的頭文件進行了重新封裝,采用無?.h
?后綴的形式(如?<cstdio>
、<cstdlib>
、<cerrno>
),以區別于 C 的傳統頭文件(如?<stdio.h>
、stdlib.h
、errno.h>
)。
<cerrno>
?是 C++ 標準化的頭文件,明確屬于 C++ 標準庫。<errno.h>
?是 C 風格的頭文件,雖然 C++ 兼容它,但不推薦在新代碼中使用。
2.?潛在的命名空間管理
理論上,<cerrno>
?將相關名稱(如?errno
、EDOM
、ERANGE
)放入?std
?命名空間,而?<errno.h>
?直接將它們暴露在全局命名空間。雖然實際實現中(由于兼容性要求):
errno
?仍然是全局宏(無法放入?std
)。EDOM
、ERANGE
?等宏通常在全局命名空間也可用。
但使用?<cerrno>
?能更清晰地表達“這是 C++ 代碼”的意圖,并可能在未來的標準中更好地支持命名空間隔離。
注意事項
errno
?仍是全局宏:即使使用?<cerrno>
,errno
?也不會變成?std::errno
(因為它是宏)。- 錯誤碼宏(如?
EDOM
):大多數實現仍允許全局訪問,但理論上可以額外通過?std::EDOM
?訪問(盡管實踐中很少需要)。
5?fork()
?系統調用詳解
fork()
?是 Unix/Linux 系統中的一個重要系統調用,用于創建一個新的進程(子進程)
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
父進程:返回子進程的?PID(進程ID,> 0)。
子進程:返回?0。
出錯時:返回?-1(并設置?errno)。
6 exit
- 是一個標準庫函數,用于終止當前進程,并返回一個狀態碼給操作系統。它是進程正常退出的標準方式
#include <stdlib.h>
void exit(int status);
參數:
status:進程的退出狀態碼:
0 或 EXIT_SUCCESS:表示成功退出。
非零值(通常 EXIT_FAILURE=1):表示失敗退出(具體含義由程序定義)
exit()
?的運行機制
(1) 進程終止流程
當調用?exit()
?時,操作系統會按順序執行以下操作:
-
調用?
atexit()
?注冊的函數(按注冊的逆序執行)。 -
刷新所有標準 I/O 緩沖區(如?
printf
?未輸出的內容會被強制寫入)。 -
關閉所有打開的文件描述符。
-
釋放進程占用的內存和其他資源。
-
向父進程發送狀態碼(可通過?
wait()
?或?$?
?獲取)。
(2)?exit()
?vs?_exit()
函數 | 說明 |
---|---|
exit() | 標準 C 庫函數,會執行清理(刷新緩沖區、調用?atexit() ?等)。 |
_exit() | 系統調用(<unistd.h> ),直接終止進程,不執行任何清理。 |
?7?snprintf
?
snprintf
?是 C 標準庫中的一個格式化輸出函數,用于安全地格式化字符串并寫入緩沖區,比傳統的?sprintf
?更安全,因為它可以防止緩沖區溢出(Buffer Overflow)
#include <stdio.h>
int snprintf(char *str, // 目標緩沖區size_t size, // 緩沖區大小(最多寫入 size-1 個字符 + '\0')const char *format, // 格式化字符串(類似 printf)... // 可變參數(要格式化的數據)
);
返回值:
成功:返回理論寫入的字符數(不包括結尾的?\0),即使緩沖區不夠。
錯誤:返回負值(如編碼錯誤)。
8?getpid()
??getppid()
getpid()
?是 Unix/Linux 系統編程中的一個基礎系統調用,用于獲取當前進程的進程ID(PID)getppid()
?是 Unix/Linux 系統調用,用于獲取當前進程的父進程 PID(Process ID)
#include <unistd.h> // 必須包含的頭文件
pid_t getpid(void); // 返回當前進程的 PID
返回值:
成功:返回當前進程的?PID(正整數)
不會失敗(無錯誤碼)#include <unistd.h> // 必須包含的頭文件
pid_t getppid(void); // 返回父進程的 PID
返回值:
成功:返回父進程的 PID(正整數)
不會失敗(無錯誤碼)
9?sizeof
?
sizeof
?是 C/C++ 中的一個編譯時運算符(不是函數!),用于計算變量、類型或表達式所占的內存大小(字節數)。它是靜態計算的,不會在運行時影響程序性能
sizeof(變量或類型)
返回值:
size_t 類型的無符號整數(通常是 unsigned int 或 unsigned long)。
計算時機:在編譯時確定,不會執行括號內的代碼(如果傳入表達式)
語法規則
操作對象 | 示例 | 是否必須加括號 | 備注 |
---|---|---|---|
變量名 | sizeof a | 可選 | 更簡潔,但可能降低可讀性 |
類型名 | sizeof(int) | 必須 | 不加括號會導致編譯錯誤 |
表達式 | sizeof(a + b) | 必須 | 表達式需用括號包裹 |
示例?
int arr[10];變量(括號可選)
size_t s1 = sizeof arr; // 計算數組總大小
size_t s2 = sizeof(arr); // 等效寫法類型(括號必須)
size_t s3 = sizeof(int); // 計算 int 類型大小表達式(括號必須)
size_t s4 = sizeof(arr[0]); // 計算數組元素大小結構體/類成員的大小
struct S { int x; double y; };
size_t s = sizeof(S::x); // C++ 中合法,計算成員大小
創建管道實操
makefile
mypipe:mypipe.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf mypipe
mypipe.cc
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>int main()
{int pipefd[2] = {0};int n = pipe(pipefd);if(n < 0){std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;return 1;}pid_t id = fork();assert(id != -1); if(id == 0){close(pipefd[0]);int cnt = 0;while(true){char x = 'X';write(pipefd[1], &x, 1);std::cout << "Cnt: " << cnt++<<std::endl;sleep(1);}close(pipefd[1]);exit(0);}close(pipefd[1]);char buffer[1024];int cnt = 0;while(true){int n = read(pipefd[0], buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = '\0';std::cout << "我是父進程, child give me message: " << buffer << std::endl;}else if(n == 0){std::cout << "我是父進程, 讀到了文件結尾" << std::endl;break;}else {std::cout << "我是父進程, 讀異常了" << std::endl;break;}sleep(1);if(cnt++ > 5) break;}close(pipefd[0]);int status = 0;waitpid(id, &status, 0);std::cout << "sig: " << (status & 0x7F) << std::endl;sleep(100);return 0;
}
?
mypipe
:目標文件(可執行文件)名稱mypipe.cc
:依賴文件(源代碼文件)g++ -o $@ $^ -std=c++11
:編譯命令$@
?表示目標文件(mypipe)$^
?表示所有依賴文件(這里只有 mypipe.cc)-std=c++11
?指定使用 C++11 標準
.PHONY:clean
:聲明 clean 是一個偽目標(不是實際文件)rm -rf mypipe
:刪除生成的可執行文件
父進程管理多個子進程實現管道通信實操
Makefile
ctrlProcess:ctrlProcess.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -rf ctrlProcess
Task.hpp
#pragma once#include <iostream>
#include <vector>
#include <unistd.h>typedef void (*fun_t)(); void a() { std::cout << "a任務正在執行...\n" << std::endl; }
void b() { std::cout << "b任務正在執行...\n" << std::endl; }
void c() { std::cout << "c任務正在執行...\n" << std::endl; }#define A 0
#define B 1
#define C 2class Task
{
public:Task(){funcs.push_back(a);funcs.push_back(b);funcs.push_back(c);}void Execute(int command){if (command >= 0 && command < funcs.size()) funcs[command]();}public:std::vector<fun_t> funcs;
};
ctrlProcess.cc
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"
using namespace std;const int gnum = 3;
Task t;class EndPoint
{
private:static int number;
public:pid_t _c_id;int _w_fd;string processname;
public:EndPoint(int id, int fd) :_c_id(id), _w_fd(fd){//process-0[pid:fd]char namebuffer[64];snprintf(namebuffer, sizeof(namebuffer), "process-%d[%d:%d]", number++, _c_id, _w_fd);processname = namebuffer;}string name() const { return processname; }
};int EndPoint::number = 0;void WaitCommand()
{while(1){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)) t.Execute(command);else if (n == 0){std::cout << "父進程關閉了寫端" << getpid() << std::endl;break;}else break;}
}void createProcesses(vector<EndPoint> *end_points)
{vector<int> fds;for(int i = 0; i < gnum; ++i){int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0); (void)n;pid_t id = fork();assert(id != -1);if (id == 0){for(auto &fd : fds) close(fd);close(pipefd[1]);dup2(pipefd[0], 0);WaitCommand();close(pipefd[0]);exit(0);}close(pipefd[0]);end_points->push_back(EndPoint(id, pipefd[1]));fds.push_back(pipefd[1]);}
}int ShowBoard()
{std::cout << "##########################################" << std::endl;std::cout << "| 0. 執行日志任務 1. 執行數據庫任務 |" << std::endl;std::cout << "| 2. 執行請求任務 3. 退出 |" << std::endl;std::cout << "##########################################" << std::endl;std::cout << "請選擇# ";int command = 0;std::cin >> command;return command;
}void ctrlProcess(const vector<EndPoint> &end_points)
{int cnt = 0;while(true){int command = ShowBoard();if (command == 3) break;if (command < 0 || command > 2) continue;int index = cnt++;cnt %= end_points.size();string name = end_points[index].name();cout << "選擇了進程: " << name << " | 處理任務: " << command << endl;write(end_points[index]._w_fd, &command, sizeof(command));sleep(1);}
}void waitProcess(const vector<EndPoint> &end_points)
{for(int i = 0; i < end_points.size(); ++i){std::cout << "父進程讓子進程退出:" << end_points[i]._c_id << std::endl;close(end_points[i]._w_fd);waitpid(end_points[i]._c_id, nullptr, 0);std::cout << "父進程回收了子進程:" << end_points[i]._c_id << std::endl;}
}// #define A 0
// #define B 1
// #define C 2int main()
{vector<EndPoint> end_points;createProcesses(&end_points);ctrlProcess(end_points);waitProcess(end_points);return 0;
}