C语言字符指针与字符串字面量——从一道“剑指 Offer”笔试题说起

  1. 字符指针 char* 与字符串字面量

  2. const char* 的含义

  3. 字符串末尾隐藏的 '\0'

  4. "abcdef"[i] 为什么合法

  5. sizeof 和 strlen 的区别

  6. 字符数组 vs 字符指针

  7. 数组名退化与地址比较

  8. == 比较地址,strcmp 比较内容

  9. 字符串字面量是否合并:标准未指定

  10. 修改字符串字面量是未定义行为

  11. 栈区、只读数据区/常量区的概念

  12. const 的三种位置

  13. C 和 C++ 对字符串字面量类型的差异

  14. 未使用变量警告

  15. 笔试题输出分析


1. 字符指针基础

你的代码:

c

复制代码
char c = 'a';
const char* pc = "abcdef";

for (i = 0; i < 6; i++)
{
    printf("%c ", "abcdef"[i]);
}

这里 "abcdef" 是一个字符串字面量。它本质上是一个字符数组,内容为:

text

复制代码
'a' 'b' 'c' 'd' 'e' 'f' '\0'

长度是 6,占用 7 个字节。

在 C 语言中,字符串字面量的类型是 char[N],但是修改它是未定义行为。所以更推荐用:

c

复制代码
const char* pc = "abcdef";

表示:pc 指向的字符内容不打算通过它修改。

pc 指向首字符 'a':

c

复制代码
pc     -> 'a'
pc + 1 -> 'b'
pc + 2 -> 'c'
...
pc[0] == 'a'
pc[1] == 'b'
*(pc + 1) == 'b'

注意:

c

复制代码
sizeof(pc);      // 指针大小,64 位通常是 8
strlen(pc);      // 6
sizeof("abcdef");// 7,包括 '\0'
strlen("abcdef");// 6,不包括 '\0'

pc 本身是一个指针变量,它可以改变指向:

c

复制代码
pc = "xyz"; // 可以,改变 pc 的指向

但不能通过它修改指向的内容:

c

复制代码
pc[0] = 'x'; // 编译错误,因为 pc 是 const char*

2. "abcdef"[i] 为什么合法?

因为字符串字面量在表达式中会退化为指向首字符的指针,所以:

c

复制代码
"abcdef"[i]

等价于:

c

复制代码
*("abcdef" + i)

也等价于:

c

复制代码
*(pc + i)
pc[i]

所以你的循环:

c

复制代码
for (i = 0; i < 6; i++)
{
    printf("%c ", "abcdef"[i]);
}

会输出:

text

复制代码
a b c d e f

如果写成 i <= 6,第 7 次会访问 '\0'。'\0' 是空字符,终端通常看不见,但它是字符串结束标志。

另外,你的代码里定义了:

c

复制代码
char c = 'a';
const char* pc = "abcdef";

但循环里没有使用 c 和 pc,有些编译器会警告"未使用变量"。更直观的写法是:

c

复制代码
for (i = 0; i < 6; i++)
{
    printf("%c ", pc[i]);
}

3. 字符数组 vs 字符指针

这是字符指针笔试题的核心。

字符数组

c

复制代码
char str1[] = "hello bit.";
char str2[] = "hello bit.";

这两个是数组。编译器会在栈上给它们各自分配空间,并把字符串内容复制进去。它们的内容相同,但地址不同。

可以修改:

c

复制代码
str1[0] = 'H'; // 可以

因为这是数组,数组里的内容属于你自己。

字符指针

c

复制代码
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";

这两个是指针变量。它们指向字符串字面量。字符串字面量通常放在只读数据区或常量区。相同内容的字面量是否合并,由编译器实现决定。

常见编译器可能会让 str3 和 str4 指向同一块地址:

text

复制代码
str3 ---> "hello bit."
str4 ---> "hello bit."

也可能不合并:

text

复制代码
str3 ---> "hello bit."  (地址 A)
str4 ---> "hello bit."  (地址 B)

标准没有规定必须相同,也没有规定必须不同。


4. 笔试题分析

你的笔试题:

c

复制代码
char str1[] = "hello bit.";
char str2[] = "hello bit.";

const char* str3 = "hello bit.";
const char* str4 = "hello bit.";

if (str1 == str2)
    printf("str1 and str2 are same\n");
else
    printf("str1 and str2 are not same\n");

if (str3 == str4)
    printf("str3 and str4 are same\n");
else
    printf("str3 and str4 are not same\n");

第一个比较

c

复制代码
str1 == str2

str1 和 str2 是数组名。在 == 中,数组名退化为指向首元素的指针。所以这里比较的是两个数组首元素的地址。

因为 str1 和 str2 是两个独立的数组,地址不同,所以:

text

复制代码
str1 and str2 are not same

第二个比较

c

复制代码
str3 == str4

str3 和 str4 是 const char* 指针,它们保存的是字符串字面量的地址。这里比较的是指针值,也就是地址。

常见编译器会合并相同字符串字面量,所以 str3 == str4 可能为真:

text

复制代码
str3 and str4 are same

但这不是标准保证的。如果编译器不合并,也可能输出:

text

复制代码
str3 and str4 are not same

所以这个笔试题的经典答案是:

text

复制代码
str1 and str2 are not same
str3 and str4 are same   // 常见环境,但不保证

如果要比较内容

应该用 strcmp:

c

复制代码
#include <string.h>

if (strcmp(str1, str2) == 0)
    printf("内容相同\n");

if (strcmp(str3, str4) == 0)
    printf("内容相同\n");

strcmp 返回 0 表示两个字符串内容相等。


5. 修改字符串字面量是未定义行为

危险写法:

c

复制代码
char* p = "hello";
p[0] = 'H'; // 未定义行为

在 C 中,char* p = "hello"; 可能只是警告,但修改 p[0] 是未定义行为,可能崩溃。因为字符串字面量通常放在只读区。

推荐写法:

c

复制代码
const char* p = "hello";
// p[0] = 'H'; // 编译错误,安全

如果要修改,用字符数组:

c

复制代码
char s[] = "hello";
s[0] = 'H'; // 正确

6. const 的三种常见位置

c

复制代码
const char* p1;       // 指向常量的指针:不能改 *p1,可以改 p1
char* const p2 = s;   // 常量指针:可以改 *p2,不能改 p2
const char* const p3 = "abc"; // 都不能改

记忆方法:

  • const 在 * 左边:修饰指向的内容。

  • const 在 * 右边:修饰指针本身。

  • 两边都有:都修饰。


7. C 和 C++ 的差异

在 C 中:

c

复制代码
char* p = "hello"; // 语法上允许,但修改是未定义行为

在 C++ 中:

c

复制代码
char* p = "hello"; // 错误,字符串字面量类型是 const char[N]

所以更推荐统一写:

c

复制代码
const char* p = "hello";

这样 C 和 C++ 都更安全。


8. 笔试题背后的内存图

text

复制代码
栈区:
str1:  'h' 'e' 'l' 'l' 'o' ' ' 'b' 'i' 't' '.' '\0'
str2:  'h' 'e' 'l' 'l' 'o' ' ' 'b' 'i' 't' '.' '\0'

只读数据区/常量区:
"hello bit." 可能只有一份,也可能有多份

str3 ---> "hello bit."
str4 ---> "hello bit."   // 可能和 str3 相同,也可能不同

关键区别:

  • str1、str2 是数组,各自有独立空间。

  • str3、str4 是指针,指向字面量。

  • == 比较地址,不是内容。

  • 内容比较用 strcmp。

  • 字面量是否合并由实现决定,不要依赖。


小结

  • "abcdef" 是字符数组,末尾有 '\0'。

  • "abcdef"[i] 等价于 *("abcdef" + i)。

  • sizeof("abcdef") == 7,strlen("abcdef") == 6。

  • 字符数组可修改,字符串字面量不可修改。

  • const char* 指向的内容不能通过该指针修改。

  • str1 == str2 比较地址,不是内容。

  • strcmp 比较字符串内容。

  • 相同字符串字面量是否合并,标准未指定,常见编译器会合并。

  • 不要写依赖字面量合并的代码。


最后速查表

表达式 类型/含义
int* arr[3] 指针数组,3 个 int*
arr 在表达式中 退化为 int**
arr[i][j] *(*(arr + i) + j)
int a[3][5] 真正二维数组
a 在表达式中 退化为 int(*)[5]
int** 二级指针,可接收指针数组名
int(*)[5] 数组指针,可接收 int[3][5]
char str[] = "abc" 字符数组,可修改
const char* p = "abc" 指向字符串字面量,不可通过 p 修改内容
str1 == str2 比较地址
strcmp(str1, str2) == 0 比较内容

一句话总结:

数组名大多数时候退化为首元素指针,但 sizeof 和 & 除外;指针数组不是二维数组,int** 也不是 int(*)[N];== 比地址,strcmp 比内容;字符串字面量能不能合并,标准说了不算,编译器说了算,所以别依赖

相关推荐
学逆向的1 小时前
C 局部静态变量
c语言·开发语言·网络安全
咸咸咸虾7 小时前
Microsoft Visual Studio 历史版本与官方安装包链接汇总
ide·microsoft·visual studio
Zwawa9 小时前
从 printf 到曲线:把数据送上 VOFA+
c语言·单片机·嵌入式
二十七日秋12 小时前
算法刷题笔记「Day 1|四道 OJ 入门题里的深坑」
c语言·汇编·数据结构·c++·笔记·python·算法
abc light13 小时前
ARM 裸机开发(1)——ARM 体系结构
c语言·arm开发
Navigator_Z13 小时前
LeetCode //C - 1309. Decrypt String from Alphabet to Integer Mapping
c语言·算法·leetcode
zuiyuewentian13 小时前
借助 AI 在 Win11 上搭建 Rocky Linux:用 Visual Studio 直接调试 Linux 服务
linux·运维·visual studio
爱聊灵光的维C14 小时前
C语言初阶:关于函数设计的详细讲解和注意事项
c语言·开发语言·青少年编程·蓝桥杯·学习方法
wuminyu14 小时前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
C++ 老炮儿的技术栈15 小时前
Linux 动态库踩坑:明明重新编译了 .so,调用函数返回值却一直不变(libfjrobot.so 版本号异常排查实录)
linux·c语言·网络·c++·visual studio