本文最后更新于:2022年11月30日 下午
C语言字符串问题
C语言中涉及到字符串有两个方面:字符串常量 和 字符串变量
1.字符串常量
①字符串常量
字符串常量的含义:字符串常量是一对双引号括起来的字符序列
1
| 如 : "abcd" ”我是常量" printf("我是字符串常量"); 双引号标起来的都是字符串常量。
|
字符串常量是什么?
字符串常量当然就是不可以变的 字符串啦!
①-①通过C语言代码理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include<stdio.h> void main() { char *a = "abcdef"; printf("%s",a); }//这个结果成功输出
#include<stdio.h> void main() { char *a = "abcdef"; printf("%s\n",a); *a = "我要更改值啦!"; printf("%s\n",a); }
q@ubuntu:~/Desktop/Str$ gcc -o test main.c main.c: In function ‘main’: main.c:6:5: warning: assignment makes integer from pointer without a cast [-Wint-conversion] *a = "我要更改值啦!"; ^ q@ubuntu:~/Desktop/Str$ ./test abcdef Segmentation fault (core dumped)
|
①-②通过汇编+内存理解
IDA打开下面代码程序
X64位程序中:
1 2 3 4 5 6 7 8 9 10
| #include<stdio.h> void main() { char *a; printf("未初始化a指针是:%p\n",a); printf("未初始化a指针指向内存空间的值:%s\n",a); a = "abcdef"; printf("初始化后a的指针是:%p\n",a); printf("初始化后a指针指向内存空间的值:%s\n",a); }
|
main函数的汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
public main main proc near
var_8= qword ptr -8
push rbp mov rbp, rsp sub rsp, 10h mov rax, [rbp+var_8] mov rsi, rax lea rdi, format mov eax, 0 call _printf mov rax, [rbp+var_8] mov rsi, rax lea rdi, byte_768 mov eax, 0 call _printf lea rax, aAbcdef mov [rbp+var_8], rax mov rax, [rbp+var_8] mov rsi, rax lea rdi, byte_7A8 mov eax, 0 call _printf mov rax, [rbp+var_8] mov rsi, rax lea rdi, byte_7C8 mov eax, 0 call _printf nop leave retn
main endp
|
兄弟们那就来吧:printf(“%s”,str); printf函数传参顺序是 rdi rsi rdx rcx …..
所以 %s 格式控制符,通过rdi传参。 str传入字符串 通过rsi传参….
咱们只用关注 这三条指令
1 2 3
| lea rdi,byte_7C8 lea rax, aAbcdef mov rsi,rax
|
所以我们 ,来看看 aAcdef地址吧
1 2
| .rodata:000000000000079A 61 62 63 64 65 66 00 aAbcdef db 'abcdef',0 ; DATA XREF: main+38↑o
|
看到没有!!!它!在 .rodata段。 rodata 就是说 read only data的意思。 就说字符串常量它只可读
②字符串常量赋值:是赋地址
前沿:字符串赋值是 赋字符串地址的,而不是 赋字符串本身的值。
我们来看看例子:
②-①通过C语言代码理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include<stdio.h> void main() { char *a; printf("未初始化a指针是:%p\n",a); printf("未初始化a指针指向内存空间的值:%s\n",a); a = "abcdef"; printf("初始化后a的指针是:%p\n",a); printf("初始化后a指针指向内存空间的值:%s\n",a); }
未初始化a指针是:(nil) 未初始化a指针指向内存空间的值:(null) 初始化后a的指针是:0x562fe5e9079a 初始化后a指针指向内存空间的值:abcdef
|
我们可以看到:
未初始化前
1 2 3 4 5
| char *a; printf("未初始化a指针是:%p\n",a); //结果: 未初始化a指针是:(nil) 未初始化a指针指向内存空间的值:(null)
|
未初始化前,指针nil,可以理解为无效指针。 指针指向的值也为空NULL
初始化后
1 2 3 4 5 6
| a = "abcdef"; printf("初始化后a的指针是:%p\n",a); printf("a指针指向内存空间的值:%s",*a); //结果 初始化后a的指针是:0x562fe5e9079a 初始化后a指针指向内存空间的值:abcdef
|
可以看到 a的指针变为:0x562fe5e9079a
🔺由此可见: 字符串赋值 实际是上 是赋了地址。
②-②通过汇编代码理解
x64架构下:待会就看看call printf 下面的vararg(代指printf,传入的 量);
看到 箭头下面的:vararg了嘛?
1 2 3
| ► 0x5555555546a0 <main+86> call printf@plt <printf@plt> format: 0x5555555547a8 ◂— 0x8ce58ba7e59d88e5 vararg: 0x55555555479a ◂— 0x666564636261 /* 'abcdef' */
|
0x666564636261 /* ‘abcdef’ */ 这个地址,就是存放abcdef 的首地址
让我们来看看:
1 2 3
| pwndbg> print /c 0x666564636261 $32 = 97 'a' //我们看到了什么? 输出了这个地址的值,是'a' 就是我们这一串字符的首地址!
|
*从这可以看出:字符串传递,依旧是以地址的形式传递的!
②-③通过内存机制理解
1
| 代码中已出现的字符串常量会保存一个不可修改的内存区域 当然有时候可能会在别的地方,如代码段等。
|
让我们看看反汇编代码:
1 2 3 4 5 6 7
| lea rax, aAbcdef mov [rbp+var_8], rax mov rax, [rbp+var_8] mov rsi, rax lea rdi, byte_7A8 mov eax, 0 call _printf
|
把 aAbcdef 这个变量地址,给到rax寄存器。可以看到aAbcdef ;是“abcdef” 常量名
1 2 3 4 5 6 7 8
| .rodata:0000000000000796 25 db 25h ; % .rodata:0000000000000797 73 db 73h ; s .rodata:0000000000000798 0A db 0Ah .rodata:0000000000000799 00 db 0 .rodata:000000000000079A 61 62 63 64 65 66 00 aAbcdef db 'abcdef',0 ; DATA XREF: main+38↑o .rodata:00000000000007A1 00 00 00 00 00 00 00 align 8 .rodata:00000000000007A8 ; const char byte_7A8 .rodata:00000000000007A8 E5 byte_7A8 db 0E5h
|
让我们来探个究竟:
1 2
| .rodata:000000000000079A 61 62 63 64 65 66 00 aAbcdef db 'abcdef',0 这是个好东西: 可以看到我们的字符串"abcdef"就在 这个地址段上
|
所以:字符串传值通过地址来传。
扩展:
1 2
| 为了提高程序内存空间利用率:编译器在编译c语言代码过程,会自动分配字符引用信息。且重复的字符串常量,会自动去重,只在符号信息中保留一个。 也就是说,程序中有n个相同字符串,实质上它们的地址都是一样的。
|
2.字符串变量
字符串变量:又称作字符数组。
如 ‘a’, ‘b’, ‘c’
可以修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <stdio.h> void main() { char str[4]; str[0]='a'; str[1]='b'; str[2]='c'; str[3]='e'; int i=0; for(i=0;i<4;i++) { printf("%c",str[i]); } printf("\n请输入要修改的字符(仅限半角字符abcderfg,.;等)\n"); for(i=0;i<4;i++) { scanf("%1c",&str[i]); } printf("来看看修改后的结果吧\n"); for(i=0;i<4;i++) { printf("%c",str[i]); } printf("\n");
}
|
1 2 3 4 5 6
| q@ubuntu:~/Desktop/Str$ ./test abce 请输入要修改的字符(仅限半角字符abcderfg,.;等) qaq^ 来看看修改后的结果吧 qaq^
|