指针与字符串:从入门到避坑

大家好,我是你们的老朋友。今天我们来聊聊指针与字符串这个话题。说实话,我在嵌入式这行摸爬滚打十几年,见过太多人在字符串和指针上栽跟头了。我自己刚入行那会儿,也曾经因为一个字符串拷贝的问题,调试了整整一个通宵。嗯,从那以后,我对这块的理解就特别深了。

字符串在C语言里,其实是个挺特殊的存在。它不像int、float那样是原生类型,而是通过字符数组来表达的。说白了,字符串就是字符数组加上一个结束标志'\0'。这个设计,既灵活又容易出问题。

字符串常量与字符数组

先说说字符串常量。你写"hello"的时候,编译器会在内存的只读区放这么一串字符。这个区域,你是不能改的。我有个同事曾经写过这样的代码:

char *p = "hello";
p[0] = 'H';  // 运行时崩溃!

为什么?因为"hello"存在只读区,你试图修改它,操作系统直接给你个段错误。我当时在做一个嵌入式项目,板子上跑着跑着就挂了,查了半天才发现是这个问题。

那字符数组呢?它是在栈上或者全局区分配的内存,你可以随便改:

char arr[] = "hello";
arr[0] = 'H';  // 没问题,arr在栈上

这里有个关键区别:arr是一个数组,它有自己的内存空间,会把"hello"拷贝过来。而p只是一个指针,指向了只读区的那个字符串。

核心区别:

  • 字符串常量:只读,不可修改,存储在代码段/只读数据段
  • 字符数组:可读写,存储在栈或全局区,是字符串常量的副本

字符指针与字符串

字符指针操作字符串,其实玩的就是地址游戏。你看这个:

char *p = "hello";
char *q = "hello";

你猜p == q是真是假?答案是:可能真,也可能假。这取决于编译器是否做了字符串常量合并。我在GCC上试过,默认是合并的,所以pq指向同一个地址。但在某些嵌入式编译器上,它们可能指向不同的地址。

我个人习惯是:绝不依赖这种实现细节。要比较字符串内容,老老实实用strcmp

字符指针的另一个常见用法是遍历字符串:

char *p = "hello";
while (*p) {
    putchar(*p);
    p++;
}

这里*p就是当前字符,当遇到'\0'时,*p为0,循环结束。简洁高效,对吧?

字符串处理函数的指针实现

很多初学者觉得strcpystrlen这些函数很神秘。其实说白了,它们就是指针操作的封装。我建议你亲手实现一遍,这样理解会深很多。

先看strlen

size_t my_strlen(const char *s) {
    const char *p = s;
    while (*p) p++;
    return p - s;
}

这里用了一个技巧:先让p跑到字符串末尾,然后两个指针相减,得到的就是字符个数。指针相减返回的是ptrdiff_t类型,表示两个指针之间的元素个数。

再看strcpy

char *my_strcpy(char *dest, const char *src) {
    char *ret = dest;
    while (*dest++ = *src++);
    return ret;
}

这段代码很经典。赋值表达式*dest++ = *src++的值就是被赋的那个字符。当拷贝到'\0'时,赋值结果为0,循环结束。注意,这里把'\0'也拷贝过去了。

我曾经踩过的坑:目标缓冲区不够大!strcpy不会检查缓冲区大小,如果源字符串比目标缓冲区长,就会发生缓冲区溢出。这是安全漏洞的常见来源。我建议用strncpy或者自己加长度检查。

最后看strcat

char *my_strcat(char *dest, const char *src) {
    char *ret = dest;
    while (*dest) dest++;
    while (*dest++ = *src++);
    return ret;
}

先找到dest的末尾,然后从那里开始拷贝src。逻辑很直接,但同样有缓冲区溢出的风险。

const char* 与 char* const

这两个东西,我当年也迷糊过一阵子。其实记住一个口诀就行:先看const修饰谁

写法 含义 示例
const char *p 指针指向的内容不可改 *p = 'A'; 非法
char * const p 指针本身不可改 p++; 非法
const char * const p 内容和指针都不可改 两者都非法

怎么记?你看const char *p,const修饰的是char,所以字符不能改。char * const p,const修饰的是p,所以指针不能改。

在实际项目中,我经常用const char *来传递字符串参数,告诉调用者:你放心,我不会改你的数据。这是一种契约,也是一种好习惯。

小技巧:从右往左读声明。比如const char * const p:p是一个const指针,指向const char。这样就不会搞混了。

知识体系图

下面这张图,是我梳理的本章知识脉络。你看一眼,心里就有谱了。

指针与字符串知识体系 字符串与指针 字符串常量(只读) 字符数组(可读写) 字符指针操作 不可修改 存储在只读区 栈/全局区 可修改内容 遍历字符串 指针运算 字符串处理函数指针实现 strlen:指针遍历 strcpy:赋值拷贝 strcat:拼接拷贝 const修饰规则

这张图把本章的核心内容串起来了。从字符串常量和字符数组的区别,到字符指针的操作,再到字符串函数的指针实现,最后是const的修饰规则。你顺着这个脉络走一遍,基本就掌握了。

总结

指针与字符串这块,说白了就是两件事:内存在哪里,能不能改。字符串常量在只读区,不能改;字符数组在栈上,能改。字符指针就是个地址,你可以用它遍历、拷贝、拼接字符串。const修饰符帮你把意图说清楚,也让编译器帮你检查错误。

我个人觉得,学C语言指针,最好的方法就是多写、多调试、多踩坑。踩过的坑,记得最牢。希望今天的分享能帮你少走一些弯路。


公众号:蓝海资料掘金营,微信deep3321