当前位置: 首页 > 后端技术 > PHP

Phper学习C兴趣介绍——为什么有些字符串这么难处理?

时间:2023-03-29 14:25:38 PHP

需求如果有这样的需求,有一个日期,我想截取它得到它的年份。我们可以使用explode和php,或者strtok$a="2019-09-1000:00:00";echostrtok($a,"-");//2019年的你可能对strtok不熟悉,它的作用是利用-拆分$a得到子串,循环调用可以达到和explode一样的效果。详情请看官方手册中的demohttps://www.php.net/manual/zh...实验实验一我之所以使用strtok是因为这个函数在C语言中也有,而这个函数比较“奇怪”,每次调用都是将字符串中找到的-替换为\0,然后返回标记字符串的首地址。#include#includeintmain(intargc,char*argv[]){chardate[]="2019-09-10";char*tmp=strtok(日期,"-");printf("%s,%p\n",tmp,(void*)tmp);//2019,0x7ffe8741bdd0printf("%s,%p\n",date,(void*)date);//2019,0x7ffe8741bdd0printf("%d,%c\n",date[4],date[4]);//0,return0;}实验2当我们使用char指针作为字符串初始化时,会发生什么?#include#includeintmain(intargc,char*argv[]){char*date="2019-09-10";char*tmp=strtok(日期,"-");printf("%s,%p\n",tmp,(void*)tmp);//2019,0x7ffe8741bdd0printf("%s,%p\n",date,(void*)date);//2019,0x7ffe8741bdd0printf("%d,%c\n",date[4],date[4]);//0,return0;}运算的结果就是我们使用指针变量作为左值时的Segmentationfault原理,当双引号字符串作为右值时,双引号背后的逻辑是:在只读区申请内存,存储字符串,在字符串末尾加'/0'返回字符串首地址,所以char*date是用双引号存储字符串返回的首地址在堆栈上。在使用strtok的时候,通过实验1我们可以看到,strtok实际上是将找到的字符串替换为\0,也就是说需要对原来的字符串进行修改。但是字符串在只读区,不能修改,所以运行时出现段错误。反之,我们的chardate[]数组用双引号初始化的原理是什么?是不是也是双引号返回常量字符串首地址,然后通过循环一个一个给char数组赋值?实验3猜想是猜想。我们通过实验来证明。#includeintmain(intargc,charconst*argv[]){char*str1="123";charstr2[]={'1','2','3'};charstr3[]={“123”};字符str4[]="123";return0;}通过objdump反汇编可以看到$gcca.c$objdump-Da.out00000000004004ed

:4004ed:55push%rbp4004ee:4889e5mov%rsp,%rbp4004f1:897dccmov%edi,-0x34(%rbp)4004f4:488975c0mov%rsi,-0x40(%rbp)4004f8:48c745f8c00540movq$0x4005c0,-0x8(%rbp)4004ff:00400500:c645f031movb$0x31,-0x10(%rbp)400504:c645f132movb$0x32,-0xf(%rbp)400508:c645f233movb$0x33,-0xe(%rbp)40050c:c745e031323300movl$0x333231,-0x20(%rbp)400513:c745d031323300movl$0x333231,-0x30(%rbp1a)40:b800000000mov$0x0,%eax40051f:5dpop%rbp400520:c3retq400521:662e0f1f840000nopw%cs:0x0(%rax,%rax,1)400528:00000040052b:0f1f440000nopl0x0(%rax,%rax,1)$objdump-j.rodata-d3.outa.out:文件格式elf64-x86-64Disassemblyofsection.rodata:00000000004005b0<_IO_stdin_used>:4005b0:0100020000000000........000000000000<4005>:...4005c0:31323300123.实验结论可以看出,第一个变量(黄色框)是用传入的地址初始化的,而这个地址4005c0就是后面的只读数据段在里面我们可以看到4005c0存储数据31323300十六进制对应的ascii码是123\0。第二个变量(红框)通过三个mov操作入栈(movb表示按字节移动)。第三个变量和第四个变量一样,都是字符串直接入栈,而不是像第一个变量那样传地址。因此,用指针初始化的字符串只能读取,不能改写;用char数组初始化的字符串,即使用双引号初始化,仍然在栈上,后面的程序可以重写。扩展C语言太坑爹了,那么每个函数怎么用,我们怎么知道传入的字符串在函数内部会不会被改变呢?其实在函数手册中可以看到一些细节,比如下面这个函数char*strchr(constchar*s,intc);char*strtok(c??har*str,constchar*delim);char*strcat(char*目标,常量字符*src);当形参为constchar*时,表示函数不会改变这块内存中的数据,可以传入栈、堆或只读区上的地址;否则,如果形式参数小心char*,你可以认为它是一个数组,它会改变传入的“string”。考虑#include#includeintmain(intargc,char*argv[]){char*date="2019";strcat(日期,“-09-10”);printf("%s,%p\n",日期,(void*)日期);return0;}肯定是运行时的Segmentationfault,因为“2019”存在,只能读取。如果将其替换为以下代码会怎样?#include#includeintmain(intargc,char*argv[]){chardate[]="2019";strcat(日期,“-09-10”);printf("%s,%p\n",日期,(void*)日期);return0;}linuxgcc可以编译运行,但其实有问题,比如我改成#include#includeintmain(intargc,char*argv[]){字符日期[]="2019";strcat(日期,“-09-1000000000000000000”);printf("%s,%p\n",日期,(void*)日期);return0;},会出现段错误,可能在你的服务器上编译运行没有报错,如果没有报错,请增加追加字符串的长度再试。(C程序这么神奇,能跑起来也不一定就没问题。)因为date初始化分配的内存不够存放连接后的字符串。我们将其重写为#include#includeintmain(intargc,char*argv[]){chardate[11]="2019";strcat(日期,“-09-10”);printf("%s,%p\n",日期,(void*)日期);return0;}这将正常工作。可怜,C语言也麻烦,一不小心写错了,难怪PHP是世界上最好的语言。安利天下无难事,只要有心,如果你觉得学C语言很难,不如让我们一起学习,一起上车吧https://segmentfault.com/ls/1……也欢迎大家关注我的公众号,不骚扰,只干原创文章