串口协议里用两个字节传温度,代码把 16 位无符号变量赋成 -1,编译过了,收到的却是 65535。uint16 是无符号 16 位整数,这个结果不是 bug,是它的定义。
它占 2 个字节,范围 0 到 65535,共 65536 个值。C 和 C++ 的 stdint.h 里有对应类型,Go 里也是同名类型,Rust 是 u16,Java 没有无符号类型,一般用 int 顶着。
uint16_t port = 8080; uint16_t sum = 60000 + 6000; // 464溢出是取模,不是报错
无符号整数溢出按 2 的 16 次方取模。65535 加 1 变 0,60000 加 6000 得 464。C 标准对无符号溢出有明确定义,有符号的 int 溢出反而是未定义行为,别混着用。
Go 里编译期常量溢出会直接报错,运行期变量相加则回绕。做计数器和累加时,先想清楚上限会不会超过 65535,超了换 uint32 或 uint64。
和 int 混用要小心
把 16 位无符号值赋给 int 一般没问题,反过来把负数塞进去就会回绕。函数参数、结构体字段类型不一致时,编译器的警告别忽略,很多线上问题就是这里来的。
做哈希或校验时,两个这样的值相加要先转成 uint32,否则中间结果溢出,校验值对不上,这点在跨语言协议里很常见。
数据库里的 smallint unsigned 范围也是 0 到 65535,和它对得上,读写时注意驱动返回的类型。
网络传输和字节序
协议里多字节整数要约定字节序。网络字节序是大端,主机多是小端,发之前用 htons、接收用 ntohs,别把两个字节拼反了。
解析二进制包时按固定偏移读,别用结构体直接强转,编译器对齐会插空字节。写协议文档时把字段宽度、字节序、单位写清楚,后面少吵很多架。
什么时候不该用它
表示端口、长度、像素坐标这类不会为负的值,这个类型合适。表示差值、温度、金额,就别用,负数会被悄悄转成大数。
一句话,uint16 的边界很明确,用之前先算最大可能值,比出了事再查强。协议字段能宽一点就别卡着上限。