Skip to content
← 返回C列表
开发实践

C POSIX 完整实践:双文件字节统计库 · C字节统计项目的输入输出是什么至C字节统计库的完整文件怎样组织 ​

返回分篇目录

先具备 C 指针、分离编译和基本所有权知识。本专题沿文件契约、线程生存期、完整项目、排错推进。示例未执行,仅完成静态文本/模板检查,不声称运行通过。

专题入口:C POSIX 文件接口:描述符、短读写与错误契约 · C POSIX 线程:创建、互斥与退出前收拢 · C POSIX 完整实践:双文件字节统计库 · C POSIX 排错:系统错误、死锁与不完整输出

C POSIX 完整实践:双文件字节统计库 ​

先具备 C 指针、分离编译和基本所有权知识。本专题沿文件契约、线程生存期、完整项目、排错推进。示例未执行,仅完成静态文本/模板检查,不声称运行通过。

专题入口:C POSIX 文件接口:描述符、短读写与错误契约 · C POSIX 线程:创建、互斥与退出前收拢 · C POSIX 完整实践:双文件字节统计库 · C POSIX 排错:系统错误、死锁与不完整输出

C字节统计项目的输入输出是什么 ​

C字节统计项目的输入输出是什么:输入 left.txt 是 abc 加 LF 共4字节,right.txt 是 de 加 LF 共3字节,预期报告 bytes=7 后接 LF,退出0。计量单位是原始字节,不是 Unicode 字符、行数或文件元数据大小;两个路径相同会重复计数。检出时若换为 CRLF,预期字节数也要改变。

项目只读输入,没有覆盖用户文件;输出写到借用的 stdout。参数个数错误退出2,库错误由 C++ 宿主转换成 stderr 诊断并退出1。错误描述受平台本地化影响,不比较整条系统文案。

C字节统计库的完整文件怎样组织 ​

C字节统计库的完整文件怎样组织:scan.h 提供不透明句柄与所有权约定,scan.c 实现两线程、互斥锁、read 和 write 循环;main.cpp 通过 C 链接声明消费它。CMakeLists.txt 把 C 文件作为 C11 编译成静态库,把宿主作为 C++20 编译并传播线程依赖。

下面每个文件直接引用仓库示例,页面不复制第二份实现。README 与 TEST-SPEC 是交付组成部分;故障注入表是待实现规格,不能把它说成自动化测试已通过。

CMakeLists.txt(同源文件,示例未执行)
text
cmake_minimum_required(VERSION 3.20)
project(byte_scan LANGUAGES C CXX)
if(NOT UNIX)
  message(FATAL_ERROR "POSIX only: use Linux, macOS, or a Linux environment in WSL")
endif()
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Threads REQUIRED)
add_library(byte_scan STATIC scan.c)
target_include_directories(byte_scan PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(byte_scan PRIVATE _POSIX_C_SOURCE=200809L)
target_compile_options(byte_scan PRIVATE -Wall -Wextra -Wpedantic)
target_link_libraries(byte_scan PUBLIC Threads::Threads)
add_executable(byte-report main.cpp)
target_link_libraries(byte-report PRIVATE byte_scan)
target_compile_definitions(byte-report PRIVATE _POSIX_C_SOURCE=200809L)
target_compile_options(byte-report PRIVATE -Wall -Wextra -Wpedantic)
include(CTest)
if(BUILD_TESTING)
  add_executable(scan-contract-test test_scan.c)
  target_link_libraries(scan-contract-test PRIVATE byte_scan)
  target_compile_options(scan-contract-test PRIVATE -Wall -Wextra -Wpedantic)
  add_test(NAME scan-contract COMMAND scan-contract-test
    ${CMAKE_CURRENT_SOURCE_DIR}/inputs/left.txt
    ${CMAKE_CURRENT_SOURCE_DIR}/inputs/right.txt)
endif()
ENVIRONMENT.md(同源文件,示例未执行)
md
# 依赖、环境与平台边界

示例未执行。C11、C++20、POSIX.1-2008接口、CMake >=3.20。
第三方依赖:无;pthread 来自目标平台线程库,由 Threads::Threads 表达。
_POSIX_C_SOURCE=200809L 在库目标定义,用于声明 strdup 等 POSIX 接口。
macOS SDK 是否接受全部选项/宏尚未实测;不宣称原生 macOS 验收。
Linux 发行版、libc、CPU 架构、C/C++编译器版本尚未选择并实测。
Windows 原生不适用;WSL 仅表示 Linux 环境路径,没有执行 WSL 验证。

当前进程不能被取消,也不能在 run 时 fork 后继续操作本句柄。
可信输入、稳定有限普通文件、单一外部调用线程是前提。
Linux close 报错时通常已释放描述符,不盲目重试;POSIX.1-2008 对
close/EINTR 后资源状态不作统一保证,本例可能在其他实现上遗留描述符。
因此跨平台不承诺 EINTR 关闭路径绝无泄漏。macOS 仅理论说明,无特定行为实测。
示例不实现 signal/cancellation 安全的通用资源层,不适合直接复制为生产SDK。

后续读者自行验证时记录:OS/架构、libc或SDK、C/C++编译器、CMake版本、
Shell、输入换行、构建日志、stdout/stderr/退出码、测试及诊断工具结果。
inputs/left.txt(同源文件,示例未执行)
text
abc
inputs/right.txt(同源文件,示例未执行)
text
de
main.cpp(同源文件,示例未执行)
cpp
#include "scan.h"
#include <csignal>
#include <exception>
#include <iostream>
#include <memory>
#include <system_error>
#include <unistd.h>

struct ScanDeleter {
    void operator()(scan *s) const noexcept { scan_destroy(s); }
};
using ScanOwner = std::unique_ptr<scan, ScanDeleter>;

static void check(int code, const char *operation) {
    if (code != 0)
        throw std::system_error(code, std::generic_category(), operation);
}

int main(int argc, char **argv) {
    if (argc != 3) {
        std::cerr << "usage: byte-report LEFT RIGHT\n";
        return 2;
    }
    // CLI policy only: let write return EPIPE instead of signal termination.
    if (std::signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
        std::cerr << "cannot set SIGPIPE policy\n";
        return 1;
    }
    try {
        scan *raw = nullptr;
        check(scan_create(argv[1], argv[2], &raw), "create");
        ScanOwner owner(raw);
        check(scan_run(owner.get()), "run");
        check(scan_report(owner.get(), STDOUT_FILENO), "report");
        return 0;
    } catch (const std::exception &e) {
        std::cerr << e.what() << '\n';
        return 1;
    }
}
README.md(同源文件,示例未执行)
md
# POSIX 字节扫描与 C++ RAII 宿主

示例未执行;未编译、未配置 CMake、未运行测试。所有结果是预期。

依赖:C11、C++20、POSIX.1-2008 的文件与 pthread API、CMake >=3.20。
不依赖第三方库,不安装依赖。目标为 Linux / macOS 的 POSIX 环境;
Windows 只能在 WSL 的 Linux 环境阅读和尝试,非 Windows 原生项目。
这些是教学要求,未固定或验证具体发行版、SDK、编译器版本组合。
静态库同工具链同架构构建;不承诺不同编译器/运行库/位数之间二进制兼容。

仓库根目录 Bash/Zsh 命令(仅供读者自行尝试,本轮未执行):

```sh
cmake -S examples/native/p2-posix-cpp -B build/native-p2
cmake --build build/native-p2
./build/native-p2/byte-report examples/native/p2-posix-cpp/inputs/left.txt examples/native/p2-posix-cpp/inputs/right.txt
ctest --test-dir build/native-p2 --output-on-failure
```

预期标准输出 `bytes=7` 后一个 LF,退出0。输入文件必须保持 LF,
left.txt 为4字节、right.txt 为3字节;CRLF 会改变字节数。
两个工作线程各自打开一个普通文件、循环读取,在同一互斥锁下提交字节数。
主线程 join 所有已创建线程,成功后才允许 report。
路径复制给库,C++ unique_ptr 用库的销毁函数回收。

只接收可信路径与有限、执行时不变的普通文件;不保证打开设备的安全性,
不提供文件快照、取消、超时、递归目录扫描或恶意输入隔离。
O_NONBLOCK 避免 FIFO 的常规等待,但不是对任意设备的安全策略。
两个输入为同一路径时会计算两遍;这是两个输入项的和,不是去重扫描。
stdout 借用不关闭;CLI 忽略 SIGPIPE 使管道关闭成为 EPIPE 错误。
report 写失败可能已有前缀输出,不能回滚。stderr 文案依系统不同。

详细失败和手工推演见 TEST-SPEC.md;不把预期当作实测。
test_scan.c 是完整但未编译、未执行的基础 API 测试驱动,CTest 预期输出
scan contracts passed 并成功退出;包含状态拒绝、重复输入、缺失路径、无效输出描述符。
TEST-SPEC 的系统调用替身故障注入尚未实现,不能声称基础测试已覆盖它们。

继续阅读