C语言字符串问题

本文最后更新于: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)

image-20221130140458842

①-②通过汇编+内存理解

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
; Attributes: bp-based frame

; int __cdecl main(int argc, const char **argv, const char **envp)
public main
main proc near

var_8= qword ptr -8

; __unwind {
push rbp
mov rbp, rsp
sub rsp, 10h
mov rax, [rbp+var_8]
mov rsi, rax
lea rdi, format ; format
mov eax, 0
call _printf
mov rax, [rbp+var_8]
mov rsi, rax
lea rdi, byte_768 ; format
mov eax, 0
call _printf
lea rax, aAbcdef ; "abcdef"
mov [rbp+var_8], rax
mov rax, [rbp+var_8]
mov rsi, rax
lea rdi, byte_7A8 ; format
mov eax, 0
call _printf
mov rax, [rbp+var_8]
mov rsi, rax
lea rdi, byte_7C8 ; format
mov eax, 0
call _printf
nop
leave
retn
; } // starts at 64A
main endp

兄弟们那就来吧:printf(“%s”,str); printf函数传参顺序是 rdi rsi rdx rcx …..

所以 %s 格式控制符,通过rdi传参。 str传入字符串 通过rsi传参….

咱们只用关注 这三条指令

1
2
3
lea rdi,byte_7C8; 这个format 就是格式控制符的指针
lea rax, aAbcdef ; 这个aAbcdef就是字符串"abcdef"指针
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
/**扩展一下:
*nil表示无值,无其他意义
*null表示空的意思
*/

我们可以看到:

未初始化前

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,传入的 量);

image-20221130121507081

​ 看到 箭头下面的: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
代码中已出现的字符串常量会保存一个不可修改的内存区域 (一般是.RODATA段)当然有时候可能会在别的地方,如代码段等。

让我们看看反汇编代码:

1
2
3
4
5
6
7
lea     rax, aAbcdef    ; "abcdef"
mov [rbp+var_8], rax
mov rax, [rbp+var_8]
mov rsi, rax
lea rdi, byte_7A8 ; format
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^

C语言字符串问题
http://example.com/2022/11/30/C语言/C语言字符串问题/
作者
Yang
发布于
2022年11月30日
更新于
2022年11月30日
许可协议