C语言字符串:字符指针char * 与 字符数组char[]区别

字符串与字符数组 — char *、char[]、strlen、sizeof

C 没有字符串类型。"字符串"就是以 \0 结尾的 char 数组。

本文从内存布局讲清 char *char[] 的区别,以及 sizeofstrlen 到底在算什么。


1 字符串的本质

// "hello" 实际占 6 字节:
// 'h' 'e' 'l' 'l' 'o' '\0'

标准库函数(strlenstrcpyprintf %s)全靠 \0 判断结尾,少了它就会越界读内存。

📌 使用字符串函数需要 #include <string.h>。推荐用 strncpy / snprintf 替代 strcpy,防止缓冲区溢出。

2 char * vs char[]

2.1 字符指针 char *

char *str = "helloworld";

字面量在只读段.rodata),str 是个指针指过去,不能改内容:

str[0] = 'H';  // 段错误

image-20260316165903109

2.2 字符数组 char[]

char str2[] = "helloworld";

栈上开 11 字节,把字面量拷贝进去,可以改:

str2[0] = 'H';  // OK

image-20260316170015772

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

image-20260316170803355


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 *pchar * const p 的区别?

A

const char *p:内容不能改,指针可以改指向。char * const p:指针不能改指向,内容可以改。嵌入式常用前者,指向 Flash 里的常量字符串。

Q8:函数里能返回局部字符数组的指针吗?

A

不能。局部数组在栈上,函数返回后栈帧回收,指针悬空。应传外部缓冲区、用 static、或 malloc