REDcker头像
关注

C++相比C多了什么 语言特性并发内存序与ABI对比

C++ 相比 C 多了什么:语言能力、并发内存序与 ABI 对比

“C++ 比 C 多了什么”表面是语法清单,实质是设计哲学差异:C 强调可移植、贴近硬件的过程式表达;C++ 在兼容 C 子集的前提下,提供多范式抽象工程化标准库,并逐步形成更完整的并发与类型系统

本文用对照表、示意代码、Mermaid 与 ASCII 分层说明:语言与类型标准库与资源管理并发与内存序ABI 与工程边界。具体行为以当前 C(C11/C17/C23)C++(C++11/17/20/23) 标准及所用编译器为准。


目录

  1. 一句话总览
  2. 关系鸟瞰:C 作为 C++ 子集(示意)
  3. 类型系统与语法对照
  4. 函数与模块化
  5. 用户自定义类型:struct / class 与 OOP
  6. 泛型:宏、void* 与模板
  7. 内存与资源:malloc、new 与 RAII
  8. 错误处理:返回值、errno 与异常
  9. I/O 与字符串
  10. 标准库与“自带工程设施”
  11. 现代 C++ 自 C++11 起的主要特性
  12. 并发与内存序:C11 与 C++11+
  13. ABI:为什么 C 稳、C++ 难
  14. 选型建议
  15. 附录:同功能左右对照(节选)
  16. 免责声明

一句话总览

维度CC++
设计取向过程式、贴近硬件多范式(过程 / OOP / 泛型 / 函数式风格)
抽象与类型安全相对弱,常靠约定与审查更强类型与编译期检查手段更多
标准库小而基础( libc )大而全( STL + 线程 + 时间 + …)
资源管理习惯手动为主RAII + 智能指针等惯用法
并发C11 起有原子与线程标准接口;工程上 pthread 仍常见<thread> / <mutex> / <atomic> 等与语言惯用法结合紧
ABI对外接口常以 C ABI 为“ lingua franca ”跨编译器/版本/标准库实现时 ABI 风险更高

关系鸟瞰:C 作为 C++ 子集(示意)

C 子集(可写在 .cpp 中)

过程式 + struct + 指针

C++ 语言能力(概念)

OOP / 类与虚函数

模板与泛型

RAII / 构造析构

STL 与 iostream

命名空间 / 引用 / 重载…

  ┌─────────────────────────────────────┐
  │            C++ 完整语言               │
  │  ┌─────────────────────────────┐   │
  │  │   与 C 高度重叠的“C 子集”     │   │
  │  │   (语法/语义仍可能有细微差异)│   │
  │  └─────────────────────────────┘   │
  └─────────────────────────────────────┘

类型系统与语法对照

主题CC++
void* 与其它指针历史上常可隐式与 T* 互转(C++ 更严格)一般需 static_cast<T*>(p) 等显式转换
boolC99 起有 _Bool / <stdbool.h>bool原生 booltruefalse
枚举enum 枚举符常泄漏到外层作用域enum class:强类型、作用域隔离
const有,但语义与用法相对朴素更细constexpr、成员 const、重载对 const 敏感等
nullptrNULL(常为 0)表示空指针nullptr 类型安全
复合字面量C99 有C++ 规则与版本相关,使用上需注意与 C 的差异

enum class 示意:

// C++:Color::RED 不会污染外层名字空间
enum class Color { RED, GREEN };
Color c = Color::RED;

函数与模块化

能力CC++
函数重载无(靠不同函数名)同名不同参列表
默认参数void f(int x = 0);
引用无(用指针)T&,部分场景语义更清晰
内联static inlineinline 与 ODR、模板结合更系统
命名空间无,靠前缀 libname_namespace 组织大型代码
隐式函数声明历史遗留问题(老代码)C++ 要求先声明后使用

用户自定义类型:struct / class 与 OOP

C 的 structC++ 的 struct / class
成员函数无(用函数指针或外部函数模拟)可有成员函数、this
访问控制public / protected / privateclass 默认 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; }

内存与资源:mallocnew 与 RAII

操作CC++
分配未初始化内存malloc::operator new(不一定等价于 new T
构造对象手动在 malloc 内存上“放置”并初始化(若模拟 C++)new T:分配并调用构造函数
释放free(不调用析构)delete:析构 + 释放
异常路径下释放易遗漏RAII:析构自动释放
// C++:离开作用域即关闭(示意)
struct File {
    FILE* fp{};
    explicit File(const char* path) : fp(std::fopen(path, "rb")) {}
    ~File() { if (fp) std::fclose(fp); }
};

错误处理:返回值、errno 与异常

风格CC++
返回错误码非常常见仍常见(尤其 API 边界)
errnolibc 错误习惯仍可用
异常 try/throw/catch有;与 RAII 组合做栈展开清理

工程上 C++ 也常采用 Expected / outcome / 错误码 风格;异常并非“必须用”,但是一种语言级机制。


I/O 与字符串

主题CC++
格式化 I/Oprintf / scanfstd::cout / std::format(C++20)等
类型安全printf 格式串与实参易不一致iostream / format 相对更安全
字符串char*strcpystd::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
线程与锁pthreadstd::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> 提供 _Atomicatomic_int 等类型,以及 memory_order_*;可用 atomic_store_explicit / atomic_load_explicit 等带内存序的原子操作。
  • C++11 起:std::atomicstd::memory_order 在工程资料与标准库封装上更成体系,并与 线程、锁、条件变量 同一套“现代 C++”叙事结合。

因此差异不仅是“有没有内存序”,而是:语言 + 标准库 + 惯用法 的整体体验。

C++ 发布-订阅示例(acquire/release)

#include <atomic>

std::atomic<int> flag{0};
int data = 0;

// 线程 A
data = 42;
flag.store(1, std::memory_order_release);

// 线程 B
if (flag.load(std::memory_order_acquire) == 1) {
    // 典型模式下可依赖与 data 的同步关系(具体证明依赖标准内存模型)
}

C11 对应思路(explicit 内存序)

#include <stdatomic.h>

atomic_int flag;
int data;

/* 线程 A */
data = 42;
atomic_store_explicit(&flag, 1, memory_order_release);

/* 线程 B */
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); // 以 C 链接导出,便于跨语言/跨编译器边界

选型建议

场景倾向
操作系统内核、极小编译单元、ABI 极敏感边界CC ABI
大型应用、复杂领域模型、强依赖 STL 与抽象C++
跨语言 SDKC ABI + C++ 实现 最常见

附录:同功能左右对照(节选)

以下片段用于并排体会同一意图在 C 与 C++ 中的写法差异:C 侧重显式状态与资源路径,C++ 侧重封装与标准库。为可读性省略部分头文件与极端错误分支;C 侧线程示例以 POSIX pthread 示意(Windows 原生开发常改用 Win32 线程或其它库,思路相同)。

C++ 风格

vector 封装 realloc 策略

RAII 流对象

lock_guard + mutex

C 风格

手工长度/capacity

fopen/fclose 分支

pthread_mutex_lock


1. 动态数组末尾追加元素

要点
C自管 data / size / capacityrealloc 失败要回滚或报错
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;
}

/* 使用:int *a=NULL; size_t n=0,cap=0; push_int(&a,&n,&cap,42); … free(a); */

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;
} // 析构关闭文件,无需手写 fclose

3. 互斥保护共享计数器(两线程各加若干次)

要点
Cpthread_mutex_*;锁配对靠人工;勿漏 unlock
C++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;
}

/* main:创建两个线程 join;此处略 */

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;
    }
}

/* main:std::thread t1(worker, n), t2(worker, n); t1.join(); t2.join(); */

附录小结表

任务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

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--