假若有這樣的一個需求,有個日期,想要截取得到其年份。咱們用 php 可使用explode
,也可使用strtok
php
$a = "2019-09-10 00:00:00"; echo strtok($a,"-"); // 2019
可能你們對strtok
不太熟悉,它的做用是用-
來分割$a
獲取子串,循環調用能夠達到和explode
差很少的效果。具體能夠看下官方手冊裏面的 demo https://www.php.net/manual/zh...linux
我之因此用strtok
呢,是由於C 語言裏也有這個函數,這個函數比較「怪」,每一次調用,是將字符串中找到的-
替換爲\0
,而後返回標記字符串的首地址。segmentfault
#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; }
當咱們使用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; }
運行的結果倒是sass
Segmentation fault
當咱們使用指針變量做爲左值
,雙引號字符串做爲右值
時,背後雙引號的邏輯是:bash
因此char * date
就在棧上存放裏雙引號字符串返回的首地址。當使用strtok
的時候,經過實驗1
能夠看到strtok
實際是找到的字符串替換爲\0
,也就是說須要修改原字符串的。而該字符串是在只讀區,不不能修改,因此運行出現了段錯誤。服務器
反過來思考,咱們 char date[]
數組經過雙引號初始化的時候又是什麼原理,是否是也是雙引號返回了常量字符串首地址,而後再經過循環一個個賦值到char
數組裏呢?函數
猜測歸猜測。咱們經過實驗來證實下。spa
#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 反彙編能夠看到.net
$ 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)
$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 是世界上最好的語言。
世上無難事只怕有心人,若是以爲想學C語言,又比較困難,不如咱們一塊兒來學,趕快上車 https://segmentfault.com/ls/1...
也歡迎你們關注個人公衆號,不發騷擾,只發乾貨原創文章