从C到Rust:基本类型 C 的隐式不确定 vs Rust 的显式确定

基本类型 ------ C 的隐式不确定 vs Rust 的显式确定


一、C 的基本类型:貌似简单,实则暗坑无数

1.1 整数的"标准"问题

C 的整数类型看起来简单,但它们的大小是平台相关的

c 复制代码
// 这些类型多大?------取决于编译器和平台

char // 总是 1 字节(但 signed 还是 unsigned 取决于实现!)

short // 至少 2 字节,通常是 2 字节

int // 至少 2 字节,"最自然"的大小------16、32、64 都可能

long // 至少 4 字节------32 位或 64 位

long long // 至少 8 字节------C99 引入,通常 8 字节

这意味着:同一段 C 代码在不同的平台上可能表现出完全不同的行为。

c 复制代码
// 这段代码在 32 位平台和 64 位平台上的行为不同!

int count = 100000;

long total = count * count;

// 在 16 位 int 上:count * count 会溢出(100000 > 32767)

// 在 32 位 int 上:没问题

// 在 64 位 int 上:没问题(但 long 是 64 位还是 32 位?也取决于平台)

1.2 隐式整数提升------C 中最隐蔽的 UB 来源之一

C 有复杂的隐式整数提升规则。当不同类型一起运算时,较小的类型被"提升"为较大的类型------但这条规则常常产生意外的结果:

c 复制代码
#include <stdio.h>

#include <stdint.h>

  


int main() {

uint16_t a = 65535;

uint16_t b = 1;

  


// 整数提升:uint16_t 被提升为 int(32位)

uint16_t c = a + b; // c = 0?还是 65536?

// 结果:65536 → 截断为 16 位 → 0

// 但如果 int 是 16 位?溢出!

printf("%u\n", c); // 0

  


// 更隐蔽的例子:

uint16_t x = 0xFFFF;

uint32_t y = (x << 16) >> 16;

// x << 16:x 被提升为 int,左移 16 位

// 如果 int 是 32 位:0xFFFF0000(int)

// (x << 16) >> 16:算术右移还是逻辑右移?

// 如果 x 是 uint16_t:期望逻辑右移

// 但提升为 int 后:可能是算术右移!

// 结果:0xFFFFFFFF 而不是 0x0000FFFF!

printf("%u\n", y); // 取决于实现

}

整数提升规则在 C 标准中占了数页,即使是经验丰富的 C 程序员也经常搞错。

1.3 有符号整数溢出是未定义行为

这是 C 中最被低估的问题之一:

c 复制代码
int i = INT_MAX;

i = i + 1; // ❌ 有符号整数溢出 ------ 未定义行为!

// 可能回绕到 INT_MIN,可能崩溃,可能被优化器完全消除

// 编译器可以假设"有符号整数不会溢出",并基于此优化代码

编译器利用这个 UB 进行激进的优化,导致看起来"不可能"的 bug:

c 复制代码
// 一个真实的例子:

int foo(int x) {

if (x > x + 1) { // 对有符号整数,这个条件永远为假(因为溢出是 UB)

return 0; // 编译器可以完全删除这个分支

}

return 1;

}

// 编译器可能直接返回 1,因为"有符号整数不会溢出"

1.4 char 的符号性不确定

c 复制代码
char c = 0xFF;

// char 是 signed 还是 unsigned?

// 取决于实现!常见平台是 signed(如 x86),ARM 可能是 unsigned

int i = c;

// 如果 char 是 signed:i = -1

// 如果 char 是 unsigned:i = 255

// 同样的代码,不同的结果!

这就是为什么很多 C 代码规范要求明确写 signed charunsigned char------但 C 标准允许实现自由选择 char 的类型。

1.5 _Bool 是整数,不是真正的布尔类型

c 复制代码
_Bool flag = 5; // 非零值 → 转换为 1

if (flag) { ... } // 没问题

  


// 但 _Bool 本质上还是整数:

int x = flag; // ✅ 可以赋值给 int

flag = 100; // ✅ 编译通过,flag 被设为 1

  


// C 中"真"是任何非零值------太宽松了

int y = 42;

if (y) { ... } // ✅ 42 被视为"真"

  


// 这导致了一些经典 bug:

int result = func();

if (result = 42) { // ❌ 本意是 ==,但 = 合法!

// 这里的条件永远为真------42 是非零值

}

1.6 浮点类型的问题

c 复制代码
float a = 0.1; // ⚠️ 0.1 是 double 字面量,隐式转换为 float

double b = a + 0.2; // 0.2 是 double,a 被提升为 double 再运算

// 可能精度损失

  


// 类型转换中的问题:

int x = 3.14; // 3.14 → 3(隐式截断,无警告)

double y = 5 / 2; // 5/2 = 2(整数除法),然后 2 → 2.0

// 本意可能是 5.0/2 得到 2.5

1.7 总结 C 的基本类型问题

arduino 复制代码
问题 1:大小不确定------int/long 在不同平台上大小不同

问题 2:隐式整数提升------复杂规则,容易出错

问题 3:整数溢出 UB------有符号溢出是未定义行为

问题 4:char 符号不确定------不同平台行为不同

问题 5:布尔是整数------if (x = 42) 合法

问题 6:隐式类型转换------精度丢失无警告

C 的基本类型系统把所有"确定"的事情都留给了平台和编译器,程序员无法从代码中看出跨平台行为。


二、Rust 的基本类型:显式、确定、安全

2.1 固定大小的整数类型

Rust 的整数类型在名字中包含了大小和符号,没有任何歧义:

rust 复制代码
// 有符号整数(i = signed integer)

i8 // 8 位有符号:-128 到 127

i16 // 16 位有符号:-32768 到 32767

i32 // 32 位有符号:-2147483648 到 2147483647

i64 // 64 位有符号

i128 // 128 位有符号

  


// 无符号整数(u = unsigned integer)

u8 // 8 位无符号:0 到 255

u16 // 16 位无符号:0 到 65535

u32 // 32 位无符号

u64 // 64 位无符号

u128 // 128 位无符号

  


// 指针大小(isize / usize)

isize // 和指针同宽(32位 = i32,64位 = i64)

usize // 和指针同宽(32位 = u32,64位 = u64)

每个类型的宽度在名字中明确写出。 i32 在任何平台上都是 32 位,u64 在任何平台上都是 64 位。

rust 复制代码
// Rust 代码的跨平台行为是确定的:

let count: i32 = 100000;

let total: i64 = (count as i64) * (count as i64);

// 在任何平台上,i32 都是 32 位,i64 都是 64 位

// 行为完全确定

2.2 确定的溢出行为

Rust 对整数溢出的处理是定义明确的------不是未定义行为:

rust 复制代码
// Debug 模式下:溢出会 panic

let x: i32 = 2_147_483_647;

// let y = x + 1; // ❌ Debug 模式 panic: attempt to add with overflow

  


// Release 模式下:默认回绕(两补码)

// 但这是定义好的行为,不是 UB

  


// 显式控制溢出行为:

let y = x.wrapping_add(1); // 回绕:-2147483648

let y = x.saturating_add(1); // 饱和:2147483647

let y = x.checked_add(1); // Option:None(不会 panic)

let y = x.overflowing_add(1); // (结果, 是否溢出)

Rust 把"溢出时怎么办"的选择权交给了程序员,而不是留给未定义行为。

2.3 没有隐式类型转换

Rust 不会自动将一种整数类型转换为另一种:

rust 复制代码
let x: i32 = 42;

// let y: i64 = x; // ❌ 编译错误!不能隐式转换 i32 → i64

let y: i64 = x as i64; // ✅ 必须显式转换

  


let a: u8 = 255;

// let b: i8 = a; // ❌ 不能隐式转换 u8 → i8

let b: i8 = a as i8; // ✅ 但注意:255 → -1(回绕)

每个类型转换都必须显式写 as 不会发生 C 中那种"不小心传错了类型,编译器自动帮你转了"的情况。

2.4 真正的布尔类型

bool 在 Rust 中是完全独立的类型,不是整数:

rust 复制代码
let flag: bool = true;

let other: bool = false;

  


// 不能从整数转换:

// let x: bool = 1; // ❌ 编译错误

let x: bool = true; // ✅

  


// 不能隐式用于算术:

// let y: i32 = flag; // ❌ 编译错误

let y: i32 = if flag { 1 } else { 0 }; // ✅ 必须显式写条件

  


// if 语句只接受 bool:

if flag { /* ... */ } // ✅

// if 42 { /* ... */ } // ❌ 编译错误------42 不是布尔值

这彻底消除了 C 中 if (x = 42) 这类经典 bug。

2.5 Unicode 字符类型

Rust 的 char 表示一个 Unicode 标量值(U+0000 到 U+10FFFF,不含代理对),不是字节:

rust 复制代码
let c: char = 'A'; // ASCII 字符

let c: char = '中'; // 中文------直接支持

let c: char = '😀'; // emoji------直接支持

let c: char = '\u{1F600}'; // 用 Unicode 码位表示

  


// char 是 4 字节(不是 1 字节!)

use std::mem::size_of;

assert_eq!(size_of::<char>(), 4);

  


// char 保证是有效的 Unicode 标量值

// 非法值无法存入 char:

// let c: char = '\u{D800}'; // ❌ 编译错误!这是代理对的一半

2.6 浮点类型

rust 复制代码
let a: f32 = 0.1; // 32 位浮点

let b: f64 = 0.1; // 64 位浮点(默认浮点字面量)

  


// 需要显式类型转换

let x: i32 = b as i32; // ✅ 显式转换 f64 → i32(截断)

浮点数的比较有专门的注意事项(NaN 不可比),Rust 通过 PartialOrd(不是 Ord)来体现这一点------在类型系统中表达了"浮点数不满足全序"的事实。


三、与 C 程序员的对话

"int 就是 int,我写了十几年没出过问题"

C 程序员 :"我一直在用 int 写代码,没遇到过你说的平台问题。"
Rust :"如果你只在 x86 Linux 上开发,int 永远是 32 位,确实很少遇到问题。但问题在于:你写的代码可能在别的平台上跑。 嵌入式(16 位 int)、老旧系统(16 位 int)、大型机(64 位 int)------你的代码在这些平台上可能默默出错。Rust 的 i32 在任何地方都是 32 位,代码的行为由你写的时候决定,而不是由编译的时候决定。"

c 复制代码
// C ------ 移植到嵌入式平台就可能出错

int buffer_size = 32768;

int* buffer = malloc(buffer_size * sizeof(int));

// 如果 int 是 16 位:32768 > 32767,buffer_size 溢出!
rust 复制代码
// Rust ------ 在任何平台上都正确

let buffer_size: i32 = 32768;

let buffer = vec![0i32; buffer_size as usize];

// i32 永远是 32 位,不管在什么平台上

"溢出就溢出呗,C 中不也正常处理?"

C 程序员:"整数溢出------注意点就好,而且大多数情况是预期行为。"
Rust :"C 的有符号溢出是未定义行为 ,不是'预期行为'。编译器会利用这一点做优化------比如 if (x > x + 1) 这个条件,因为在 C 中有符号整数不会溢出(否则是 UB),编译器假定它永远为假,直接删除分支。实际运行时 x 溢出了------但编译器已经删除了检查代码。这种 bug 极难排查。"

c 复制代码
// C ------ 编译器的优化可能让你惊讶

int check_overflow(int x) {

if (x + 1 < x) { // 检查溢出的常见模式

return -1; // 发生了溢出

}

return 0;

}

// 编译器分析:有符号整数溢出是 UB,所以 x+1 < x 永远为假

// 优化结果:直接 return 0,溢出检查被删除了!
rust 复制代码
// Rust ------ 溢出行为明确,不会被优化掉

fn check_overflow(x: i32) -> i32 {

match x.checked_add(1) {

Some(_) => 0, // 没有溢出

None => -1, // 发生了溢出

}

}

// 不会被优化删掉------checked_add 的语义是确定的

"所有的类型转换都要写 as 不麻烦吗?"

C 程序员 :"Rust 要显式写 as 来转换类型,不觉得麻烦吗?C 的隐式转换方便多了。"
Rust :"方便是一方面,但隐式转换掩盖了 bug。C 中 double d = 5 / 2; 会得到 2.0 而不是 2.5------因为 5/2 是整数除法。这种 bug 在 C 中很常见。Rust 的显式转换在写的时候多了几个字符,但读代码的时候省了几个小时。"

c 复制代码
// C ------ 隐式转换掩盖了问题

double ratio = 5 / 2; // 2.0,不是 2.5!

// 本意可能是:5.0 / 2
rust 复制代码
// Rust ------ 显式转换让你清楚知道自己在做什么

let ratio: f64 = 5 / 2; // ❌ 编译错误:不能将 i32 赋值给 f64

let ratio: f64 = 5.0 / 2.0; // ✅ 2.5

let ratio: f64 = 5 as f64 / 2; // ✅ 2.5(但注意 2 是 i32,需要转换)

五、小结

5.1 C 的基本类型:6 个问题

markdown 复制代码
1. 大小不确定 int / long 在不同平台上不同

2. 隐式整数提升 复杂规则,难以预测

3. 溢出未定义行为 有符号整数溢出 = UB

4. char 符号不确定 signed/unsigned 取决于实现

5. 布尔是整数 if (x = 42) 合法

6. 隐式类型转换 精度丢失无警告

5.2 Rust 的基本类型:6 个解决

rust 复制代码
1. 每个类型名含宽度 i32 / u64 / f32 ------ 所有平台一致

2. 无隐式提升 每个运算在类型内进行

3. 溢出行为确定 Debug panic / Release wrapping + checked/wrapping API

4. char 是 Unicode 4 字节,有效的 Unicode 标量值

5. bool 是独立的 不是整数,不能赋值给整数,不能用于算术

6. 显式类型转换 所有转换通过 as 关键字

5.3 一句话总结

C 的基本类型"看起来简单但实则不确定"------大小取决于平台、溢出取决于编译器、char 的符号取决于实现。Rust 的基本类型"看起来名字长但实则确定"------每个类型在名字里就告诉了你它有多大,行为在任何平台上都是一致的。C 把确定性的责任推给了平台和程序员,Rust 把确定性写在了类型名里和编译器中。

相关推荐
清晨很温柔啊2 小时前
# 用 Rust 手搓 AI 自演化主板:当 18 个异构器官长出 C++ 骨骼
rust
星栈1 天前
我用 Rust + Dioxus 做了个全栈跨平台笔记应用:第一版先把列表和详情跑通
前端·rust·前端框架
doiito1 天前
【Agent Harness】Gliding Horse 工具结果压缩体系:如何用“指针”驯服上下文膨胀
ai·rust·架构设计·系统设计·ai agent
星栈2 天前
Dioxus 接数据库最容易写歪的 3 个地方:sqlx + SQLite 怎么接才顺
前端·rust·前端框架
独孤留白2 天前
从C到Rust:移动语义、引用传递与生命周期——一次讲清楚
rust
星栈2 天前
Dioxus 表单处理:从输入、校验到文件上传,一条链路讲透
前端·rust·前端框架
doiito2 天前
【Agent Harness】Gliding Horse 上下文动态感知与智能压缩:让 Agent 真正“听得进”每一句话
ai·rust·架构设计·系统设计·ai agent
Bigger2 天前
Tauri (26)——托盘图标总对不上系统主题?一行 Template Image 搞定
前端·rust·app