REDcker
2026-09-15 18:34:47 点赞:0 阅读:0
关注
C++ 相比 C 多了什么:语言能力、并发内存序与 ABI 对比
“C++ 比 C 多了什么”表面是语法清单,实质是设计哲学 差异:C 强调可移植、贴近硬件的过程式 表达;C++ 在兼容 C 子集的前提下,提供多范式抽象 与工程化标准库 ,并逐步形成更完整的并发与类型系统 。
本文用对照表、示意代码、Mermaid 与 ASCII 分层说明:语言与类型 、标准库与资源管理 、并发与内存序 、ABI 与工程边界 。具体行为以当前 C(C11/C17/C23) 与 C++(C++11/17/20/23) 标准及所用编译器为准。
目录
一句话总览 关系鸟瞰:C 作为 C++ 子集(示意) 类型系统与语法对照 函数与模块化 用户自定义类型:struct / class 与 OOP 泛型:宏、void* 与模板 内存与资源:malloc、new 与 RAII 错误处理:返回值、errno 与异常 I/O 与字符串 标准库与“自带工程设施” 现代 C++ 自 C++11 起的主要特性 并发与内存序:C11 与 C++11+ ABI:为什么 C 稳、C++ 难 选型建议 附录:同功能左右对照(节选) 免责声明
一句话总览
维度 C C++ 设计取向 过程式、贴近硬件 多范式(过程 / OOP / 泛型 / 函数式风格) 抽象与类型安全 相对弱,常靠约定与审查 更强类型与编译期检查手段更多 标准库 小而基础( libc ) 大而全( STL + 线程 + 时间 + …) 资源管理习惯 手动为主 RAII + 智能指针等惯用法并发 C11 起有原子与线程标准接口;工程上 pthread 仍常见 <thread> / <mutex> / <atomic> 等与语言惯用法结合紧ABI 对外接口常以 C ABI 为“ lingua franca ” 跨编译器/版本/标准库实现时 ABI 风险更高
关系鸟瞰:C 作为 C++ 子集(示意)
┌─────────────────────────────────────┐
│ C++ 完整语言 │
│ ┌─────────────────────────────┐ │
│ │ 与 C 高度重叠的“C 子集” │ │
│ │ (语法/语义仍可能有细微差异)│ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
类型系统与语法对照
主题 C C++ void* 与其它指针历史上常可隐式与 T* 互转(C++ 更严格) 一般需 static_cast<T*>(p) 等显式转换 boolC99 起有 _Bool / <stdbool.h> 的 bool 原生 bool、true、false 枚举 enum 枚举符常泄漏到外层作用域enum class :强类型、作用域隔离const有,但语义与用法相对朴素 更细 :constexpr、成员 const、重载对 const 敏感等nullptr用 NULL(常为 0)表示空指针 nullptr 类型安全复合字面量 C99 有 C++ 规则与版本相关,使用上需注意与 C 的差异
enum class 示意:
enum class Color { RED, GREEN } ;
Color c = Color:: RED;
函数与模块化
能力 C C++ 函数重载 无(靠不同函数名) 同名不同参列表 默认参数 无 void f(int x = 0);引用 无(用指针) T&,部分场景语义更清晰内联 static inline 等inline 与 ODR、模板结合更系统命名空间 无,靠前缀 libname_ namespace 组织大型代码隐式函数声明 历史遗留问题(老代码) C++ 要求先声明后使用
用户自定义类型:struct / class 与 OOP
点 C 的 struct C++ 的 struct / class 成员函数 无(用函数指针或外部函数模拟) 可有成员函数、this 访问控制 无 public / protected / private(class 默认 private)构造 / 析构 无 构造函数、析构函数 继承与多态 手写 vtable 模拟 virtual、运行时多态
class Animal {
public :
virtual void speak ( ) { }
virtual ~ Animal ( ) = default ;
} ;
class Dog : public Animal {
public :
void speak ( ) override { }
} ;
泛型:宏、void* 与模板
手段 优点 典型问题 C 宏 无运行时开销 无类型、难调试、易污染名字 void* + sizeof + 回调灵活 易错、靠人工保证类型一致 C++ 模板 类型安全、可编译期求值(constexpr) 编译慢、错误信息冗长
template < class T >
T max_of ( T a, T b) { return a > b ? a : b; }
内存与资源:malloc、new 与 RAII
操作 C C++ 分配未初始化内存 malloc::operator new(不一定等价于 new T)构造对象 手动在 malloc 内存上“放置”并初始化(若模拟 C++) new T :分配并调用构造函数释放 free(不调用析构)delete :析构 + 释放异常路径下释放 易遗漏 RAII :析构自动释放
struct File {
FILE* fp{ } ;
explicit File ( const char * path) : fp ( std:: fopen ( path, "rb" ) ) { }
~ File ( ) { if ( fp) std:: fclose ( fp) ; }
} ;
错误处理:返回值、errno 与异常
风格 C C++ 返回错误码 非常常见 仍常见(尤其 API 边界) errnolibc 错误习惯 仍可用 异常 try/throw/catch 无 有;与 RAII 组合做栈展开清理
工程上 C++ 也常采用 Expected / outcome / 错误码 风格;异常并非“必须用”,但是一种语言级 机制。
I/O 与字符串
主题 C C++ 格式化 I/O printf / scanfstd::cout / std::format(C++20)等类型安全 printf 格式串与实参易不一致iostream / format 相对更安全 字符串 char*、strcpy 等std::string 动态增长与接口
标准库与“自带工程设施”
C 常见:libc + 手写数据结构
C++ 常见:libc + libstdc++/libc++/MS STL + 算法/容器/线程
能力 C 常见路径 C++ 常见路径 动态数组 malloc/realloc + 长度字段std::vector关联表 手写哈希 / 第三方库 std::unordered_map / std::map排序查找 qsort / bsearch 或手写std::sort / std::binary_search线程与锁 pthread std::thread / std::mutex时间 time.hstd::chrono
现代 C++ 自 C++11 起的主要特性
特性 作用直觉 auto减少冗长类型名 范围 for 遍历容器更简洁 Lambda 就地定义回调 右值引用 / std::move 资源转移、减少拷贝 智能指针 显式所有权、减少泄漏 constexpr编译期计算与约束 Concepts(C++20) 模板约束更可读 Modules(C++20) 改善编译边界(≠ ABI 银弹)
并发与内存序:C11 与 C++11+
结论先说清
C11 起:<stdatomic.h> 提供 _Atomic、atomic_int 等类型,以及 memory_order_* ;可用 atomic_store_explicit / atomic_load_explicit 等带内存序的原子操作。C++11 起:std::atomic 与 std::memory_order 在工程资料与标准库封装上更成体系,并与 线程、锁、条件变量 同一套“现代 C++”叙事结合。
因此差异不仅是“有没有内存序”,而是:语言 + 标准库 + 惯用法 的整体体验。
C++ 发布-订阅示例(acquire/release)
# include <atomic>
std:: atomic< int > flag{ 0 } ;
int data = 0 ;
data = 42 ;
flag. store ( 1 , std:: memory_order_release) ;
if ( flag. load ( std:: memory_order_acquire) == 1 ) {
}
C11 对应思路(explicit 内存序)
# include <stdatomic.h>
atomic_int flag;
int data;
data = 42 ;
atomic_store_explicit ( & flag, 1 , memory_order_release) ;
if ( atomic_load_explicit ( & flag, memory_order_acquire) == 1 ) {
}
常见 memory_order 对照(C++ 命名;C11 枚举同名思想)
内存序 直觉 relaxed只关心原子性,几乎不管顺序 acquire / release成对建立“先发生于”关系 acq_rel读改写一条指令上的组合 seq_cst全局顺序一致,最保守
线程 B
共享内存
线程 A
线程 B
共享内存
线程 A
写 data
release 写 flag
acquire 读 flag
读 data(与同步配对时可见)
ABI:为什么 C 稳、C++ 难
ABI 决定二进制能否对接 :调用约定、符号修饰、对象布局、异常传播、标准库内部结构等。
C:为什么常成为“跨边界标准”
原因 说明 对象模型简单 无类布局、vtable 符号名简单 一般接近源函数名 生态惯性 OS、动态库、FFI 长期以 C ABI 暴露
C++:高风险点
风险 后果 Name mangling 同名重载经修饰后符号不同;编译器间规则不同 类布局 / vtable 继承、虚函数、对齐填充导致布局差异 异常与 RTTI 运行时实现相关 标准库实现 libstdc++ / libc++ / MS STL 内部对象布局不同
C 调用: add ----符号表----> add
C++ 调用: foo(int) / foo(double) ----mangling----> _Z3fooi / _Z3food (示意,非唯一规则)
工程实践:对外 C ABI,对内 C++
extern "C" int api_add ( int a, int b) ;
选型建议
场景 倾向 操作系统内核、极小编译单元、ABI 极敏感边界 C 或 C ABI 大型应用、复杂领域模型、强依赖 STL 与抽象 C++ 跨语言 SDK C ABI + C++ 实现 最常见
附录:同功能左右对照(节选)
以下片段用于并排体会 同一意图在 C 与 C++ 中的写法差异:C 侧重显式状态与资源路径 ,C++ 侧重封装与标准库 。为可读性省略部分头文件与极端错误分支;C 侧线程示例以 POSIX pthread 示意 (Windows 原生开发常改用 Win32 线程或其它库,思路相同)。
1. 动态数组末尾追加元素
侧 要点 C 自管 data / size / capacity,realloc 失败要回滚或报错 C++ vector 内聚策略与异常/强保证(依实现与操作而定)
C(示意):
# include <stdlib.h>
static int push_int ( int * * data, size_t * len, size_t * cap, int x) {
if ( * len >= * cap) {
size_t ncap = * cap ? * cap * 2u : 4u ;
int * nd = ( int * ) realloc ( * data, ncap * sizeof ( int ) ) ;
if ( ! nd) return - 1 ;
* data = nd;
* cap = ncap;
}
( * data) [ ( * len) ++ ] = x;
return 0 ;
}
C++(示意):
# include <vector>
std:: vector< int > a;
a. push_back ( 42 ) ;
2. 读文件并确保关闭
侧 要点 C 每条错误路径都要记得 fclose,常用 goto cleanup 或层层 if C++ 流对象析构关闭句柄;失败用 bool / 异常策略由项目约定
C(示意):
# include <stdio.h>
int read_byte_count ( const char * path, long * out_bytes) {
FILE * fp = fopen ( path, "rb" ) ;
if ( ! fp) return - 1 ;
if ( fseek ( fp, 0 , SEEK_END ) != 0 ) { fclose ( fp) ; return - 2 ; }
long sz = ftell ( fp) ;
fclose ( fp) ;
if ( sz < 0 ) return - 3 ;
* out_bytes = sz;
return 0 ;
}
C++(示意):
# include <fstream>
bool read_byte_count ( const char * path, long & out_bytes) {
std:: ifstream in ( path, std:: ios:: binary | std:: ios:: ate) ;
if ( ! in) return false ;
out_bytes = static_cast < long > ( in. tellg ( ) ) ;
return true ;
}
3. 互斥保护共享计数器(两线程各加若干次)
侧 要点 C pthread_mutex_*;锁配对靠人工;勿漏 unlockC++ std::mutex + lock_guard / unique_lock;作用域结束自动解锁
C + pthread(示意):
# include <pthread.h>
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
static long g_count = 0 ;
static void * worker ( void * arg) {
int n = * ( int * ) arg;
for ( int i = 0 ; i < n; ++ i) {
pthread_mutex_lock ( & g_mu) ;
g_count++ ;
pthread_mutex_unlock ( & g_mu) ;
}
return NULL ;
}
C++(示意):
# include <mutex>
# include <thread>
std:: mutex g_mu;
long g_count = 0 ;
void worker ( int n) {
for ( int i = 0 ; i < n; ++ i) {
std:: lock_guard< std:: mutex> lock ( g_mu) ;
++ g_count;
}
}
附录小结表
任务 C 常见负担 C++ 常见收益 动态数组 realloc、容量策略、错误码vector 统一接口文件句柄 多出口路径下配对关闭 RAII 自动释放 锁 显式 lock/unlock 作用域守卫类
免责声明
本文为技术对比与学习整理。C 与 C++ 兼容细节 (例如 const、链接、inline、VLA 等)随标准版本变化;ABI 与优化级别 强相关。生产结论请以目标工具链文档与实测为准。
主题:C、C++、类型系统、模板、RAII、并发、内存序、ABI。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/u011487024/article/details/160197837