什么是字面量?代码中的常量表示方式解析

字符串字面量详解

1. 什么是字符串字面量?

字符串字面量就是直接写在代码里的字符串,用双引号括起来:

"Hello, World!" // 这是一个字符串字面量

"ABC" // 这也是

"123" // 这还是

"" // 空字符串字面量

2. 内存中的位置

字符串字面量存储在内存的只读数据段(通常称为.rodata段)

内存布局示例


代码段 ← 存放程序指令

(.text)


只读数据段 ← 存放字符串字面量

(.rodata) "Hello"、"ABC"等在这里


已初始化数据 ← 全局变量、静态变量

(.data)


未初始化数据段 ← 未初始化的全局变量

(.bss)


堆 ← malloc分配的内存

(heap)


栈 ← 局部变量、函数参数

(stack)


3. 关键特性

特性1:只读性

char *str = "Hello"; // "Hello"在.rodata段,只读

str[0] = 'h'; // 运行时错误!试图修改只读内存

// Segmentation fault (core dumped)

特性2:生命周期

// 字符串字面量的生命周期是整个程序运行期间

char* get_greeting() {

return "Hello"; // 可以返回,因为"Hello"在.rodata中

} // 不会像局部变量那样被销毁

// 对比:局部数组

char* get_bad_greeting() {

char local[] = "Hello"; // 在栈上创建数组

return local; // 危险!函数返回后local被销毁

}
返回局部数组的危险性将在文章的尾部做介绍

特性3:唯一性(可能被合并)

char *s1 = "Hello";

char *s2 = "Hello";

// 编译器可能让s1和s2指向同一个地址

printf("s1地址: %p\n", s1); // 例如: 0x4005f4

printf("s2地址: %p\n", s2); // 也是: 0x4005f4

// 因此:

printf("%d\n", s1 == s2); // 可能是(相同地址)

4. 字符串字面量 vs 字符数组

示例1:字符串字面量

char *str_literal = "Hello"; // 指向只读内存

// "Hello"在.rodata段

// 内存布局:

// rodata段: 'H' 'e' 'l' 'l' 'o' '\0'

// str_literal → 指向这个位置
示例2:字符数组

char str_array[] = "Hello"; // 在栈上创建可修改的副本

// 等价于:char str_array[] = {'H','e','l','l','o','\0'};

// 内存布局:

// 栈上: str_array[0]='H', str_array[1]='e', ...

5. 可视化对比

#include <stdio.h>

int main() {

// 情况1:字符串字面量(只读)

char *literal1 = "Hello";

char *literal2 = "Hello";

// 情况2:字符数组(可修改)

char array1[] = "Hello";

char array2[] = "Hello";

printf("=== 地址对比 ===\n");

printf("literal1: %p\n", literal1); // 相同地址(指向.rodata)

printf("literal2: %p\n", literal2); // 相同地址(指向.rodata)

printf("array1: %p\n", array1); // 不同地址(在栈上)

printf("array2: %p\n", array2); // 不同地址(在栈上)

printf("\n=== 可修改性测试 ===\n");

// 尝试修改

array1[0] = 'h'; // 可以,因为array1在栈上

printf("修改array1后: %s\n", array1); // 输出: hello

// literal1[0] = 'h'; // 取消注释会崩溃!

// printf("%s\n", literal1);

printf("\n=== 大小测试 ===\n");

printf("sizeof(literal1) = %zu\n", sizeof(literal1)); // 8(指针大小)

printf("sizeof(array1) = %zu\n", sizeof(array1)); // 6(数组大小:"Hello" + '\0' = 6字节)

return 0;

}
可能的输出:

=== 地址对比 ===

literal1: 0x4005f4

literal2: 0x4005f4 ← 相同地址!

array1: 0x7ffd1234abcd

array2: 0x7ffd1234abcb ← 不同地址!

=== 可修改性测试 ===

修改array1后: hello

=== 大小测试 ===

sizeof(literal1) = 8

sizeof(array1) = 6

6. const的重要性

最佳实践:使用const

const char *str = "Hello"; // 好习惯:加上const

str[0] = 'h'; // 编译错误!编译器会报错
为什么const重要?

// 没有const - 容易犯错

char *str = "Hello"; // 看起来str指向可修改内存

str[0] = 'h'; // 运行时才崩溃

// 有const - 编译器保护

const char *str = "Hello"; // 明确告诉编译器:这是只读的

str[0] = 'h'; // 编译时就报错,不会等到运行时

7. 常见的坑和错误

错误1:返回局部字符串字面量的指针(没问题)

// 这是可以的

const char* get_error_message(int code) {

switch(code) {

case 1: return "File not found";

case 2: return "Permission denied";

default: return "Unknown error";

}

}

// 字符串字面量在.rodata,不会被销毁
错误2:修改字符串字面量

//错误!

char *filename = "config.txt";

filename[0] = 'C'; // 尝试改成"Config.txt" - 会崩溃!

//正确做法:

char filename[] = "config.txt"; // 创建副本

filename[0] = 'C'; // 修改副本
错误3:比较字符串字面量

char *input = get_user_input(); // 假设用户输入"Hello"

//错误:比较地址而不是内容

if (input == "Hello") { // 比较指针地址

// 可能永远不会成立

}

//正确:比较内容

if (strcmp(input, "Hello") == 0) {

// 比较字符串内容

}

9. 总结

特性 字符串字面量 char *p = "abc" 字符数组 char a[] = "abc"

内存位置 只读数据段(.rodata) 栈(局部)或.data段(全局)

可修改性 不可修改 可修改

sizeof 指针大小(通常8字节) 数组大小(字符数+1)

赋值 赋值的是地址 不能直接赋值给其他数组

相同字面量 可能共享同一内存 总是独立副本

生命周期 整个程序运行期 作用域内

推荐声明 const char *p = "abc" char a[] = "abc"

"双引号里是字面量,只读内存别乱改"

"想要修改用数组,或者malloc动态开"

记住:字符串字面量就像刻在石碑上的字,你只能读不能改。如果想要修改,需要自己准备一张纸(数组或动态内存)来抄写它。

ps:返回局部数组的危险性

示例1:立即使用可能"正常"

#include <stdio.h>

char* get_bad_greeting() {

char local[] = "Hello";

printf("函数内:local地址 = %p, 内容 = %s\n", local, local);

return local; // 危险!

}

int main() {

char *ptr = get_bad_greeting();

//立即访问 - 可能看起来"正常"(但实际上危险)

printf("函数外:ptr地址 = %p, 内容 = %s\n", ptr, ptr);

return 0;

}
可能输出

函数内:local地址 = 0x7ffd1234abcd, 内容 = Hello

函数外:ptr地址 = 0x7ffd1234abcd, 内容 = Hello ← 看起来正常!
示例2:调用其他函数后被破坏

#include <stdio.h>

#include <string.h>

char* get_bad_greeting() {

char local[] = "Hello";

return local;

}

void innocent_function() {

char buffer[100];

strcpy(buffer, "这段文字会覆盖栈上的内容!");

printf("Innocent: %s\n", buffer);

}

int main() {

char *ptr = get_bad_greeting();

// 调用另一个函数

innocent_function();

// 现在再看ptr指向的内容

printf("Main: ptr内容 = %s\n", ptr); // 可能输出乱码!

return 0;

}
可能的输出

Innocent: 这段文字会覆盖栈上的内容!

Main: ptr内容 = @#$%^&* ← 乱码!
示例3:多次调用产生奇怪结果

#include <stdio.h>

char* get_bad_greeting() {

char local[] = "Hello";

return local;

}

char* get_another_greeting() {

char local[] = "World";

return local; // 同样危险!

}

int main() {

char *ptr1 = get_bad_greeting();

char *ptr2 = get_another_greeting();

// 观察奇怪的现象

printf("ptr1 = %s\n", ptr1); // 可能输出"World"!

printf("ptr2 = %s\n", ptr2); // 可能输出"World"

// 更奇怪的是:

printf("ptr1 == ptr2 ? %s\n", ptr1 == ptr2 ? "是" : "否");

// 可能输出"是"!两个指针指向相同地址

return 0;

}
可能的输出:

ptr1 = World ← 应该是Hello,但被覆盖了!

ptr2 = World

ptr1 == ptr2 ? 是 ← 两个函数返回相同地址!

正确做法1:返回字符串字面量(只读)

const char* get_greeting() {

return "Hello"; //安全:字符串字面量在.rodata段

}

// 使用:

const char *str = get_greeting();

printf("%s\n", str); //正常

// str[0] = 'h'; //编译错误(因为有const)

正确做法1:使用静态变量

char* get_static_greeting() {

static char greeting[] = "Hello"; // 在.data段,不在栈上

return greeting; // 安全:静态变量生命周期是整个程序

}

// 注意:静态变量会被所有调用共享

char *s1 = get_static_greeting();

strcpy(s1, "Hi"); // 修改了静态变量

char *s2 = get_static_greeting();

printf("%s\n", s2); // 输出"Hi",不是"Hello"!

正确做法2:动态分配内存

char* get_dynamic_greeting() {

char *greeting = malloc(6 * sizeof(char)); // 在堆上分配

if (greeting != NULL) {

strcpy(greeting, "Hello");

}

return greeting; // 安全:堆内存持续存在

}

// 使用后必须释放!

char *str = get_dynamic_greeting();

if (str != NULL) {

printf("%s\n", str);

free(str); // 非常重要!

}

核心要点

1.栈内存是临时工:函数返回就"解雇"

2.返回栈地址就像给朋友你租的酒店房间钥匙:你退房后,朋友拿钥匙开门的后果不可预测

3.悬空指针的危害:

  • 读取:得到垃圾数据
  • 写入:可能破坏其他数据或导致崩溃
  • 安全隐患:可能泄露敏感信息

这种bug特别危险,因为:

  • 有时"正常"工作,掩盖了问题
  • 在开发环境可能正常,生产环境才出问题
  • 可能被黑客利用来读取敏感数据或执行恶意代码
相关推荐
偷星星的贼112 小时前
C++中的访问者模式实战
开发语言·c++·算法
gjxDaniel2 小时前
A+B问题天堂版
c++·算法·字符串·字符数组
M__332 小时前
动态规划进阶:简单多状态模型
c++·算法·动态规划
未来之窗软件服务2 小时前
计算机等级考试—Dijkstra(戴克斯特拉)& Kruskal(克鲁斯卡尔)—东方仙盟
算法·计算机软考·仙盟创梦ide·东方仙盟
Hcoco_me2 小时前
大模型面试题89:GPU的内存结构是什么样的?
人工智能·算法·机器学习·chatgpt·机器人
N.D.A.K3 小时前
CF2138C-Maple and Tree Beauty
c++·算法
im_AMBER3 小时前
Leetcode 104 两两交换链表中的节点
笔记·学习·算法·leetcode
程序员-King.3 小时前
day159—动态规划—打家劫舍(LeetCode-198)
c++·算法·leetcode·深度优先·回溯·递归
小雨下雨的雨3 小时前
禅息:在鸿蒙与 Flutter 之间寻找呼吸的艺术
算法·flutter·华为·重构·交互·harmonyos