范围
P34-P60
笔记
1 C语言 字符串的用法
初始化
字符串:可以以字符数组表达,也可以以字符指针表达。
直接进行数组值初始化(初始化内容长度可以小于数组长度),将数据填入字符数组中。
char text1[6] = {'H', 'E', 'L', 'L', 'O','\0'};
char text2[5] = {'H', 'E', 'L', 'L', 'O'}; // 可以通过这样的方式避免""符号自动加\0
char text3[6] = "HEL"; // 初始化值可以小于数组长度
char text4[] = "HELLO";
char text5[6] = "HELLO"; // 这句与上句等效
char *text6 = "HELLO"; // 此句与上句不等效
char *text7 = {'H', 'E', 'L', 'L', 'O'}; // 此句非法,无法将int数组隐式转换为char*
text5为char[6]
,为栈中的一段内存(若为局部变量),此处"HELLO"作为立即数1280066888直接被move写入这段内存中。
text6为char*
,数据段中存储字符串HELLO,将HELLO的地址赋给text6指针。
修改值
text1[3] = '0'; // 字符数组只能对其中单个元素进行操作
text1[6] = '0'; // 手动将\0替换为'0'
printf("%s", text6); // 对于char[],printf会以size为准,不会越界
*text6 = "HELLOOOOOOOOOOOOO"; // 此句合法,字符串指针描述的字符串可以缩放
text6 = & "ABCDEFG"; // 此句合法,但是会报warning:
// 等号右侧为char(*)[8],左侧为char *。现在text6为"ABCDEFG\0"。
text6[7] = '0' // 手动将\0替换为'0'
printf("%s", text6); // 段错误(核心已转储),由于text6中没有\0所以会不停地读取,内存越界了。
编译器行为
见注释。(都写在注释里了)
char text1[6] = {'H', 'E', 'L', 'L', '1','\0'};
char text2[6] = "HELL2";
char * text3 = "HELL3";
int main()
{
char text4[6] = {'H', 'E', 'L', 'L', '4','\0'};
char text5[6] = "HELL5";
char * text6 = "HELL6";
text1[0] = '0';
text2[0] = '0';
text3[0] = '0';
text4[0] = '0';
text5[0] = '0';
text6[0] = '0';
}
以下是经过注释的gcc汇编代码(未打开编译器优化):
.file "string.c"
.text # 又称code segment,包含可执行程序(即本程序)
# text1
.globl text1
.data # data段(存储经初始化的全局变量)
.type text1, @object
.size text1, 6
text1:
.string "HELL1"
# text2
.globl text2
.type text2, @object
.size text2, 6
text2:
.string "HELL2"
# text3
.globl text3
.section .rodata
.LC0:
.string "HELL3"
.section .data.rel.local,"aw"
.align 8
.type text3, @object
.size text3, 8
text3:
.quad .LC0
# text6
.section .rodata
.LC1:
.string "HELL6"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp # 压栈(R[rsp]-=4, M[R[rsp]] = R[rbp]),调用者的帧指针(rbp)入栈保护。rsp是堆栈指针寄存器,指向栈顶(低)位置(栈向低内存区扩张)
.cfi_def_cfa_offset 16 # CFI指令调试用,不生成代码
.cfi_offset 6, -16
movq %rsp, %rbp # 将当前函数栈指针(栈顶)写入帧指针中
.cfi_def_cfa_register 6
subq $32, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
# text4
movl $1280066888, -20(%rbp) # 立即数寻值,小端法将1280066888(LLEH)存入rbp寄存器存有的内存地址-20中
movw $52, -16(%rbp) # 将0x34,即'4'存在-16中
# text5
movl $1280066888, -14(%rbp) # 立即数寻值,小端法将1280066888(LLEH)存入rbp寄存器存有的内存地址-14中
movw $53, -10(%rbp) # 将0x35,即'5'存在-10中
# text6
leaq .LC1(%rip), %rax # 括号取出rip寄存器中存有的地址,LC1为偏移量,.LC1(%rip)找到LC1。
# leaq效果为“右=&(左)”,将LC1字符串所在的地址存在rax寄存器中,
movq %rax, -32(%rbp) # 再将rax寄存器中存放的地址存到rbp-32变量(指针)中
# text1[0] = '0';
movb $48, text1(%rip)
# text2[0] = '0';
movb $48, text2(%rip)
# text3[0] = '0';
movq text3(%rip), %rax
movb $48, (%rax)
# text4[0] = '0';
movb $48, -20(%rbp)
# text5[0] = '0';
movb $48, -14(%rbp)
# text6[0] = '0';
movq -32(%rbp), %rax # 从指针(变量区rbp-32)中取出指向的地址,存至rax寄存器中
movb $48, (%rax) # 将'0'存入rax寄存器中存有的地址中(即text6[0]的地址中)
movl $0, %eax
movq -8(%rbp), %rdx
xorq %fs:40, %rdx
je .L3
call [email protected]
.L3:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
2 一些基础的汇编知识
AT&T 汇编指令
AT&T汇编指令:gcc输出的GAS(GNU Assembler)汇编代码符合AT&T汇编指令,里面有各种常见操作解释说明,供参考。
linux汇编知识总结(GAS和NASM汇编):与上文类似,包含GAS与NASM的使用方法与区别。
x64 cheatsheet:比较好用的cheatsheet,功能和上文类似
数据段
Linux段管理,BSS段,data段,.rodata段,text段
栈指针与帧指针 函数调用
帧指针使得访问函数的参数很容易。所以任何函数调用进来的第一件事都是保护调用者的帧指针,以使得返回时可以恢复调用者的帧指针,
即
pushl %ebp
movl %esp %ebp
有了上面这两个命令,函数就可返回了,返回时只要
leave
或
movl %ebp &esp
popl %ebp
————————————————
版权声明:此段为CSDN博主「Wanderer001」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:栈指针&& 帧指针详解
图片转自这篇文章:x86-64 下函数调用及栈帧原理
上面这个链接详细讲述了函数调用的栈帧原理,是顶级好文明,建议通读。
x86-64常见寄存器说明
16个通用寄存器:
- 每个寄存器的用途并不是单一的。
- %rax 通常用于存储函数调用的返回结果,同时也用于乘法和除法指令中。在imul 指令中,两个64位的乘法最多会产生128位的结果,需要 %rax 与 %rdx 共同存储乘法结果,在div 指令中被除数是128 位的,同样需要%rax 与 %rdx 共同存储被除数。
- %rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。
- %rbp 是栈帧指针,用于标识当前栈帧的起始位置
- %rdi, %rsi, %rdx, %rcx,%r8, %r9 六个寄存器用于存储函数调用时的6个参数(如果有6个或6个以上参数的话)。
- 被标识为 “miscellaneous registers” 的寄存器,属于通用性更为广泛的寄存器,编译器或汇编程序可以根据需要存储任何数据。
3 extern关键字与头文件之间的区别和联系
4 C语言中的位运算
布尔运算|对&有分配律,a|(b&c)=(a|b)&(a|c)
布尔运算&对|有分配律,a&(b|c)=(a&b)|(a&c)
^(XOR)运算中,每个元素的逆元是它本身,即a^a=0
5 逻辑运算符
如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值。
0&&(5/0) = 0(False)
6 移位运算
左移:从左往右算,即a<<b<<c=(a<<b)<<c
右移:逻辑右移(补0),算数右移(补原数据最高位值)
无符号数必须逻辑右移,有符号数一般算数右移
7 强制类型转换
整数 无符号 <-> 有符号
同样字长的有符号数与无符号数之间强制类型转换,位值不变,改变了解释这些位的方式。
创建一个无符号常落,必须加上U或u。
e.g. 12345U
或0x1A2bu
整数 扩大范围
无符号数扩大范围时直接左边补0
有符号数扩大范围时左边补原数据最高有效位(bit)的值
整数 缩小范围
截断:直接裁掉
问题
- 感觉看了半天 最大的感受就是“永远不要用unsigned”,不过JeffQean说OI里经常有必要要用,这么一想,oi确实……时空的较量。