Phper 学 C 兴趣入门 -为什么有时候字符串的处理这么难

简介: Phper 学 C 兴趣入门 -为什么有时候字符串的处理这么难

需求

假如有这样的一个需求,有个日期,想要截取获得其年份。我们用 php 可以使用explode,也可以使用strtok

$a = "2019-09-10 00:00:00";
echo strtok($a,"-"); // 2019

可能大家对strtok不太熟悉,它的作用是用-来分割$a获取子串,循环调用可以达到和explode差不多的效果。具体可以看下官方手册里面的 demo https://www.php.net/manual/zh/function.strtok.php

实验

实验1

我之所以用strtok呢,是因为C 语言里也有这个函数,这个函数比较“怪”,每一次调用,是将字符串中找到的-替换为\0,然后返回标记字符串的首地址。

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {

    char date[] = "2019-09-10";
    char *tmp   = strtok(date, "-");

    printf("%s,%p\n", tmp, (void *) tmp);   // 2019,0x7ffe8741bdd0
    printf("%s,%p\n", date, (void *) date); // 2019,0x7ffe8741bdd0
    printf("%d,%c\n", date[4], date[4]);    // 0,

    return 0;
}

实验2

当我们使用char指针来作为字符串的初始化时,又会是怎样呢?

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {

    char *date = "2019-09-10";
    char *tmp  = strtok(date, "-");

    printf("%s,%p\n", tmp, (void *) tmp);   // 2019,0x7ffe8741bdd0
    printf("%s,%p\n", date, (void *) date); // 2019,0x7ffe8741bdd0
    printf("%d,%c\n", date[4], date[4]);    // 0,

    return 0;
}

运行的结果却是

Segmentation fault

原理

当我们使用指针变量作为左值,双引号字符串作为右值时,背后双引号的逻辑是:

  • 在只读区申请内存,存放字符串
  • 在字符串尾加上了'/0'
  • 返回字符串的首地址

所以char * date就在栈上存放里双引号字符串返回的首地址。当使用strtok的时候,通过实验1可以看到strtok实际是找到的字符串替换为\0,也就是说需要修改原字符串的。而该字符串是在只读区,不不能修改,所以运行出现了段错误。

反过来思考,我们 char date[]数组通过双引号初始化的时候又是什么原理,是不是也是双引号返回了常量字符串首地址,然后再通过循环一个个赋值到char数组里呢?

实验3

猜想归猜想。我们通过实验来证明下。

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char *str1  = "123";
    char str2[] = {'1','2','3'};
    char str3[] = {"123"};
    char str4[] = "123";
 
    return 0;
}

通过objdump 反汇编可以看到

$ gcc a.c
$ objdump -D a.out
00000000004004ed <main>:
  4004ed:    55                       push   %rbp
  4004ee:    48 89 e5                 mov    %rsp,%rbp
  4004f1:    89 7d cc                 mov    %edi,-0x34(%rbp)
  4004f4:    48 89 75 c0              mov    %rsi,-0x40(%rbp)
  4004f8:    48 c7 45 f8 c0 05 40     movq   $0x4005c0,-0x8(%rbp)
  4004ff:    00
  400500:    c6 45 f0 31              movb   $0x31,-0x10(%rbp)
  400504:    c6 45 f1 32              movb   $0x32,-0xf(%rbp)
  400508:    c6 45 f2 33              movb   $0x33,-0xe(%rbp)
  40050c:    c7 45 e0 31 32 33 00     movl   $0x333231,-0x20(%rbp)
  400513:    c7 45 d0 31 32 33 00     movl   $0x333231,-0x30(%rbp)
  40051a:    b8 00 00 00 00           mov    $0x0,%eax
  40051f:    5d                       pop    %rbp
  400520:    c3                       retq
  400521:    66 2e 0f 1f 84 00 00     nopw   %cs:0x0(%rax,%rax,1)
  400528:    00 00 00
  40052b:    0f 1f 44 00 00           nopl   0x0(%rax,%rax,1)

image.png

$objdump -j .rodata -d 3.out

a.out:     file format elf64-x86-64


Disassembly of section .rodata:

00000000004005b0 <_IO_stdin_used>:
  4005b0:    01 00 02 00 00 00 00 00                             ........

00000000004005b8 <__dso_handle>:
    ...
  4005c0:    31 32 33 00                                         123.

实验结论

可以看到
第一个变量(黄色框)初始化是传入了一个地址,而这个地址4005c0正是下面只读数据段里面的,我们可以看到下面4005c0 储存数据31323300十六进制对应的ascii码里面的就是123\0
第二个变量(红色框)是通过三次mov操作放到了栈上(movb表示按字节移动)。
第三个变量和第四个变量的方式一样,都是直接把字符串传递到了栈上,而不是像第一个变量那样,传递的是一个地址。

所以,用指针初始化的字符串在只读取,不能被改写;用 char 数组形式初始化的字符串,即使使用了双引号来初始化,也是在栈上,后面程序是可以改写的。

扩展

C 语言也太坑爹了,这样每个函数怎么用,我们怎么知道传入的字符串在函数内部会不会做变更呢?
其实在函数手册可以看到一些细节,比如下面的函数

char *strchr(const char *s, int c);
char *strtok(char *str, const char *delim);
char *strcat(char *dest, const char *src);

当形参为const char *的时候,说明函数不会对该段内存里的数据做变更,传入栈上、堆上、只读区的地址都行;反之,如果形参为char *就要小心了,可以认为它的意思是数组,会改变传入的“字符串”。

思考

根据我们上面分析的

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {

    char *date = "2019";
    strcat(date, "-09-10");
    printf("%s,%p\n", date, (void *) date);

    return 0;
}

运行时肯定是Segmentation fault了,因为“2019”是存在了只读取。

如果换成下面的代码,又会怎样呢?

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {

    char date[] = "2019";
    strcat(date, "-09-10");
    printf("%s,%p\n", date, (void *) date);

    return 0;
}

linux gcc 编译可运行,但是实际是有问题的,比如我改成

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {

    char date[] = "2019";
    strcat(date, "-09-1000000000000000000");
    printf("%s,%p\n", date, (void *) date);

    return 0;
}

就会出现段错误,也许在你的服务器编译运行又不报错,如果不报错请增加追加字符串的长度然后尝试。(C 程序就是这么神奇,能运行不一定表示没问题。)
因为date初始化分配的内存不足以存放连接之后的字符串。我们改写为

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {

    char date[11] = "2019";
    strcat(date, "-09-10");
    printf("%s,%p\n", date, (void *) date);

    return 0;
}

这样就可以正常运行了。坑爹啊,C 语言也太麻烦了,一不小心就写错,怪不得 PHP 是世界上最好的语言。

目录
相关文章
|
1天前
|
云安全 人工智能 运维
阿里云SecOps Agent,全新安全跨产品执行体验
自然语言驱动 云安全中心/WAF/CFW/ 等多款安全产品联动
1569 1
|
11天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
12天前
|
JavaScript 定位技术 API
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
CodeGraph 是一款爆火的本地代码智能工具,通过 tree-sitter 解析 AST 构建结构化知识图谱(存于 SQLite),为编程 Agent 提前生成“代码地图”。它显著降低 Agent 在中大型项目中的探索成本——实测工具调用减少71%、Token 降57%、速度提升46%,支持19+语言及主流框架路由识别,完全离线、无需 API Key。
855 11
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
|
12天前
|
人工智能 运维 JavaScript
阿里云Qoder CN(原通义灵码)全解析 产品形态、版本划分与技术适配说明
在AI辅助开发与智能办公工具持续普及的当下,阿里云旗下原通义灵码正式更名为Qoder CN,同时延伸出QoderWork CN、Qoder CN CLI、Qoder CN Mobile等多款配套产品,形成覆盖代码开发、日常办公、终端交互、移动端使用的完整工具矩阵。Qoder CN核心定位为AI智能编码助手,深度适配主流代码编辑器、集成开发环境以及终端场景;QoderWork CN则偏向桌面端综合办公辅助,二者面向不同使用场景,划分了多个版本档位,搭配差异化资源配额、功能权限与计费规则,同时兼容多款主流大模型。
884 8
|
1天前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
367 2
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
|
12天前
|
JSON 缓存 安全
通过 CC Switch 本地路由让 Codex CLI 接入 DeepSeek 等第三方模型
CC Switch 通过本地路由(`127.0.0.1:15721`)实现协议转换:将 Codex 的 Responses API 请求自动映射为 DeepSeek 等厂商的 Chat Completions 接口,兼容流式响应与工具调用,无需修改 Codex 源码,安全隔离 API Key。(239字)
2427 7
通过 CC Switch 本地路由让 Codex CLI 接入 DeepSeek 等第三方模型
|
12天前
|
存储 安全 Java
AgentScope Java 2.0:打造分布式、企业级智能体底座
AgentScope 2.0 面向分布式部署、稳定运行、权限安全等企业级需求全面升级,打造支持多租户隔离与长期稳定运行的企业级智能体底座。
|
8天前
|
人工智能 自然语言处理 算法
阿里云百炼Qwen 3.7 Plus与Max实测全解:性价比与多模态能力、成本深度对比
2026年,阿里云百炼平台推出的Qwen 3.7系列成为企业与开发者落地AI应用的核心选择,其中Qwen 3.7 Max与Plus作为两大旗舰版本,定位差异显著:Max是纯文本推理旗舰,专注高强度智能体与复杂逻辑任务;Plus则是多模态全能版,在保留强大文本能力的同时,补齐图像、视频理解能力,且价格大幅降低。本文基于2026年最新实测数据,从核心参数、文本能力、多模态能力、智能体表现、性价比与场景选型六大维度,全面解析两款模型的差异,为用户提供精准选型参考。
434 0