C 语言的数据类型系统是所有后续知识的基础。理解关键字、类型大小、符号性以及类型转换规则,是写出正确、可移植代码的前提。
C90(ANSI C)定义了 32 个关键字:
auto break case char const continue
default do double else enum extern
float for goto if inline int
long register return short signed sizeof
static struct switch typedef union unsigned
void volatile while
C99 新增:
_Alignas _Alignof _Atomic _Bool _Complex _Generic
_Imaginary _Noreturn _Static_assert _Thread_local
C11 新增:
_Alignas _Alignof _Atomic _Bool _Complex _Generic
_Imaginary _Noreturn _Static_assert _Thread_local
| 类型 | 最小大小 | 典型大小(64 位系统) |
|---|---|---|
| char | 1 字节 | 1 字节 |
| short | 2 字节 | 2 字节 |
| int | 2 字节 | 4 字节 |
| long | 4 字节 | 4 或 8 字节 |
| long long | 8 字节 | 8 字节 |
| float | 4 字节 | 4 字节 |
| double | 8 字节 | 8 字节 |
| long double | 8 字节 | 16 字节 |
signed(char 除外,其符号性由编译器决定)在表达式中,比 int 小的类型(char、short)会被隐式提升为 int。这是 C 语言设计的一个重要规则。
当不同类型混合运算时,遵循usual arithmetic conversions:较小类型向较大类型转换,有符号向无符号转换(当无符号类型秩 ≥ 有符号类型时)。
<stdint.h>C99 引入 <stdint.h>,提供平台无关的固定宽度整数类型:
int8_t / uint8_t:精确 8 位int16_t / uint16_t:精确 16 位int32_t / uint32_t:精确 32 位int64_t / uint64_t:精确 64 位intmax_t / uintmax_t:最大宽度intptr_t / uintptr_t:指针可安全存储的整数sizeof(type) // 返回类型所占字节数
sizeof expression // 返回表达式结果类型的大小
#include <stdint.h>
#include <inttypes.h> // 提供 PRId64 等格式化宏
const // 只读
volatile // 禁止编译器优化(硬件寄存器、信号处理)
restrict // C99,指针别名优化(仅用于指针)
#include <stdio.h>
int main(void) {
printf("=== C 基本数据类型大小 ===\n");
printf("char: %zu 字节\n", sizeof(char));
printf("short: %zu 字节\n", sizeof(short));
printf("int: %zu 字节\n", sizeof(int));
printf("long: %zu 字节\n", sizeof(long));
printf("long long: %zu 字节\n", sizeof(long long));
printf("float: %zu 字节\n", sizeof(float));
printf("double: %zu 字节\n", sizeof(double));
printf("long double: %zu 字节\n", sizeof(long double));
printf("\n=== 有符号与无符号范围 ===\n");
printf("signed char: [%d, %d]\n",
(int)((signed char)-128),
(int)((signed char)127));
printf("unsigned char: [0, %u]\n",
(unsigned int)((unsigned char)255));
printf("signed int: [%d, %d]\n", INT_MIN, INT_MAX);
printf("unsigned int: [0, %u]\n", UINT_MAX);
return 0;
}
编译命令:
gcc -std=c99 -Wall -Wextra -o data_types data_types.c
./data_types
#include <stdio.h>
int main(void)
{
const char a = 100;
const char b = 50;
const char c = a + b; /* 整型提升:a+b 计算时先提升为 int,结果截断回 char */
printf("char a = %d, char b = %d\n", a, b);
printf("a + b (int) = %d\n", a + b);
printf("c = a + b (char) = %d (溢出!\n", c);
/* 有符号与无符号混合比较 */
const int signed_val = -1;
const unsigned int unsigned_val = 1;
printf("\n signed -1 < unsigned 1 ? %s\n",
signed_val < unsigned_val ? "Yes" : "No");
/* 结果是 No!因为 有符号向无符号转换, 即 -1 被转换为 unsigned 的 UINT_MAX */
return 0;
}
编译命令:
gcc -std=c99 -Wall -Wextra -o promotion promotion.c
./promotion
<stdint.h>#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void) {
int8_t i8 = -128;
uint8_t u8 = 255;
int16_t i16 = -32768;
uint16_t u16 = 65535;
int32_t i32 = -2147483648;
uint32_t u32 = 4294967295U;
int64_t i64 = -9223372036854775807LL - 1;
uint64_t u64 = 18446744073709551615ULL;
printf("=== 固定宽度整数类型 ===\n");
printf("int8_t: %" PRId8 " (size: %zu)\n", i8, sizeof(i8));
printf("uint8_t: %" PRIu8 " (size: %zu)\n", u8, sizeof(u8));
printf("int16_t: %" PRId16 " (size: %zu)\n", i16, sizeof(i16));
printf("uint16_t: %" PRIu16 " (size: %zu)\n", u16, sizeof(u16));
printf("int32_t: %" PRId32 " (size: %zu)\n", i32, sizeof(i32));
printf("uint32_t: %" PRIu32 " (size: %zu)\n", u32, sizeof(u32));
printf("int64_t: %" PRId64 " (size: %zu)\n", i64, sizeof(i64));
printf("uint64_t: %" PRIu64 " (size: %zu)\n", u64, sizeof(u64));
/* uintptr_t:存储指针的整数类型 */
int x = 42;
uintptr_t ptr_as_int = (uintptr_t)&x;
printf("\nuintptr_t can hold a pointer: %p -> %" PRIuPTR "\n",
(void *)&x, ptr_as_int);
return 0;
}
编译命令:
gcc -std=c99 -Wall -Wextra -o stdint stdint_demo.c
./stdint
char 的符号性不确定:char 在某些编译器上是有符号的,在另一些上是无符号的。如果需要明确符号性,使用 signed char 或 unsigned char。sizeof 是运算符,对表达式求值时不执行表达式(除了 VLA)。无符号数的陷阱:
unsigned int len = 0;
if (len - 1 > 0) { ... } // 永远为真!因为 0-1 回绕到 UINT_MAX
整型提升的隐蔽影响:
char c = 0xFF;
unsigned char uc = 0xFF;
printf("%d\n", c); // -1(有符号提升)
printf("%d\n", uc); // 255(无符号提升)
long long 至少 64 位:C 标准保证 long long 不小于 64 位,但 long 在不同平台上可能是 4 或 8 字节。
<stdint.h> 不是所有平台都有:嵌入式平台可能不完整,检查编译器文档。
Q1:char、signed char、unsigned char 三者有什么区别?
A:char 的符号性由编译器/平台决定,可能等同于 signed char 或 unsigned char。signed char 明确有符号(-128 到 127),unsigned char 明确无符号(0 到 255)。需要精确控制符号性时,显式使用 signed 或 unsigned 修饰。
Q2:sizeof 的结果在不同平台上可能不同吗?
A:是的。int 在 16 位系统上可能是 2 字节,在 32/64 位系统上通常是 4 字节。long 在 Windows 64 位上是 4 字节,在 Linux 64 位上是 8 字节。使用 <stdint.h> 的固定宽度类型可以避免这种移植性问题。
Q3:什么是整型提升?为什么要这样设计?
A:C 语言在表达式计算时,char 和 short 会被隐式转换为 int(如果 int 能容纳所有值)。这是 C 语言早期设计决定——当时 CPU 运算以字(word)为单位最高效,提升到 int 可以统一运算宽度,简化编译器和硬件设计。
Q4:无符号数参与混合运算时会发生什么?
A:遵循 usual arithmetic conversions:如果无符号类型的秩 ≥ 有符号类型,有符号值会被转换为无符号。例如 int(-1) < unsigned int(1) 结果为 false,因为 -1 被转换为 UINT_MAX,远大于 1。
Q5:为什么要使用 <stdint.h> 而不是 int、long?
A:int、long 在不同平台上的大小不固定。嵌入式、网络协议、文件格式等场景需要精确的位宽保证。int32_t、uint64_t 等保证了精确的位宽,使代码可移植且意图明确。