C语言字符串:字符指针char * 与 字符数组char[]区别
- C语言进阶
- 11小时前
- 28热度
- 0评论
字符串与字符数组 — char *、char[]、strlen、sizeof
C 没有字符串类型。"字符串"就是以 \0 结尾的 char 数组。
本文从内存布局讲清 char * 和 char[] 的区别,以及 sizeof 和 strlen 到底在算什么。
1 字符串的本质
// "hello" 实际占 6 字节:
// 'h' 'e' 'l' 'l' 'o' '\0'
标准库函数(strlen、strcpy、printf %s)全靠 \0 判断结尾,少了它就会越界读内存。
#include <string.h>。推荐用 strncpy / snprintf 替代 strcpy,防止缓冲区溢出。
2 char * vs char[]
2.1 字符指针 char *
char *str = "helloworld";
字面量在只读段(.rodata),str 是个指针指过去,不能改内容:
str[0] = 'H'; // 段错误

2.2 字符数组 char[]
char str2[] = "helloworld";
栈上开 11 字节,把字面量拷贝进去,可以改:
str2[0] = 'H'; // OK

2.3 区别
char *str = "hello" |
char str[] = "hello" |
|
|---|---|---|
| 存储 | 指针在栈,字符串在只读段 | 整个数组在栈上 |
| 可修改 | 不能 | 能 |
sizeof |
指针大小(4/8) | 数组大小(含 \0) |
| 重新赋值 | str = "other" 可以 |
不行 |
const char *,编译器会将其放入 .rodata 区域,存放在 Flash 中,不占 RAM。
3 常见错误
// 错:char *strv2[] 是「字符指针数组」
char *strv2[] = "helloworld"; // 编译报错
// 字符指针数组——存多个字符串
char *names[] = {"Alice", "Bob", "Charlie"};

4 \0 终止符
'\0' = 0 = '\x00',占 1 字节,标记字符串结尾。
char s1[] = "hello"; // 自动补 \0,6 字节
char s2[] = {'h','e','l','l','o'}; // 不补 \0,5 字节,strlen 未定义行为
char s3[] = {'h','e','l','l','o','\0'}; // 手动补,6 字节
char buf[5] = "hello"; — 5 格放了 hello,没位置给 \0。缓冲区大小 = 字符串长度 + 1。
5 sizeof vs strlen
- sizeof:编译期运算符,返回总字节数,含 \0
- strlen:运行时函数,扫到
\0停,返回字符个数,不含 \0
char arr[] = "helloworld";
char *ptr = "helloworld";
char buf[32] = "hi";
char raw[] = {'A','B','C'};
sizeof(arr) = 11 strlen(arr) = 10
sizeof(ptr) = 8 strlen(ptr) = 10 // 指针大小,和字符串无关
sizeof(buf) = 32 strlen(buf) = 2
sizeof(raw) = 3 strlen(raw) = ??? // 没有 \0,未定义行为
char arr[] = "hello";
内存: [ h ][ e ][ l ][ l ][ o ][ \0 ]
strlen → 5 数到 \0 前停
sizeof → 6 整块内存大小
sizeof |
strlen |
|
|---|---|---|
| 本质 | 编译期运算符 | 运行时函数 |
含 \0 |
是 | 否 |
对 char * |
指针大小 | 字符串长度 |
对 char[] |
数组大小 | 字符串长度 |
6 数组退化为指针
数组传入函数后退化成指针,sizeof 拿到的是指针大小:
void foo(char str[]) {
sizeof(str); // 8,指针大小,不是数组大小
}
要知道缓冲区大小,额外传长度参数:
void process(const char *buf, size_t buf_size);
7 嵌入式实战
安全字符串操作:
strcpy(buf, input); // 不安全,溢出风险
strncpy(buf, input, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; // strncpy 不保证补 \0
snprintf(buf, sizeof(buf), "%s", input); // 推荐,自动截断 + 补 \0
编译期算长度:
#define CMD "AT+RST\r\n"
#define CMD_LEN (sizeof(CMD) - 1) // 编译期常量,免 strlen 开销
8 练习
Q1:判断对错 — char *p = "abc"; 和 char a[] = "abc"; 在内存中完全一样。
A
错。char *p 指向只读段,char a[] 在栈上拷贝了一份,存储位置和可修改性都不同。
Q2:判断对错 — sizeof("hello") 的值是 5。
A
错。sizeof("hello") = 6,含末尾 \0。
Q3:判断对错 — char buf[5] = "hello"; 中 buf 包含 \0。
A
错。5 字节放了 h e l l o,没空间给 \0。
Q4:判断对错 — void foo(char s[100]) 函数内 sizeof(s) 等于 100。
A
错。退化为指针,sizeof(s) 是 4 或 8。
Q5:写出输出
char s1[] = "hello";
char *s2 = "hello";
char s3[10] = "hi";
printf("%zu %zu\n", sizeof(s1), strlen(s1));
printf("%zu %zu\n", sizeof(s2), strlen(s2));
printf("%zu %zu\n", sizeof(s3), strlen(s3));
A
```
6 5
8 5 (32位系统为 4 5)
10 2
```
Q6:找 Bug — 这段代码在 STM32 上偶尔打印乱码:
void send_greeting(void) {
char msg[5];
strcpy(msg, "hello");
uart_send_string(msg);
}
A
msg 5 字节,"hello\0" 要 6 字节,strcpy 越界写了 1 字节。改成 char msg[6] 或用 snprintf。
Q7:const char *p 和 char * const p 的区别?
A
const char *p:内容不能改,指针可以改指向。char * const p:指针不能改指向,内容可以改。嵌入式常用前者,指向 Flash 里的常量字符串。
Q8:函数里能返回局部字符数组的指针吗?
A
不能。局部数组在栈上,函数返回后栈帧回收,指针悬空。应传外部缓冲区、用 static、或 malloc。