Loading... # 范围 P34-P60 # 笔记 ## 1 C语言 字符串的用法 ### 初始化 字符串:可以以字符数组表达,也可以以字符指针表达。 直接进行数组值初始化(初始化内容长度可以小于数组长度),将数据填入字符数组中。 ```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指针。 ### 修改值 ```C 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所以会不停地读取,内存越界了。 ``` ### 编译器行为 见注释。(都写在注释里了) ```C 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汇编代码(未打开编译器优化): ```python .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 __stack_chk_fail@PLT .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汇编指令](https://www.cnblogs.com/jokerjason/p/9578646.html):gcc输出的GAS(GNU Assembler)汇编代码符合AT&T汇编指令,里面有各种常见操作解释说明,供参考。 [linux汇编知识总结(GAS和NASM汇编)](https://blog.csdn.net/luhao19980909/article/details/103691295):与上文类似,包含GAS与NASM的使用方法与区别。 [x64 cheatsheet](https://cs.brown.edu/courses/cs033/docs/guides/x64_cheatsheet.pdf):比较好用的cheatsheet,功能和上文类似 ### 数据段 [linux数据段文档](https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/specialsections.html) [Linux段管理,BSS段,data段,.rodata段,text段](https://blog.csdn.net/wdxin1322/article/details/40512071) [Data segment - Wikipedia](https://en.wikipedia.org/wiki/Data_segment) ### 栈指针与帧指针 函数调用 > 帧指针使得访问函数的参数很容易。所以任何函数调用进来的第一件事都是保护调用者的帧指针,以使得返回时可以恢复调用者的帧指针, > > 即 > > `pushl %ebp` > > `movl %esp %ebp` > > 有了上面这两个命令,函数就可返回了,返回时只要 > > `leave` > > 或 > > `movl %ebp &esp` > > `popl %ebp` > > ———————————————— > > 版权声明:此段为CSDN博主「Wanderer001」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 > > 原文链接:[栈指针&& 帧指针详解](https://blog.csdn.net/weixin_36670529/article/details/102320641) ![](https://cdn.jsdelivr.net/gh/MapleWithered/img-storehouse@latest/picgo/img-2021-09-02-22-51-02.png) 图片转自这篇文章:[x86-64 下函数调用及栈帧原理](https://zhuanlan.zhihu.com/p/27339191) 上面这个链接详细讲述了函数调用的栈帧原理,是顶级好文明,建议通读。 ### x86-64常见寄存器说明 16个通用寄存器: ![](https://cdn.jsdelivr.net/gh/MapleWithered/img-storehouse@latest/picgo/img-2021-09-02-22-48-11.png) 1. 每个寄存器的用途并不是单一的。 1. %rax 通常用于存储函数调用的返回结果,同时也用于乘法和除法指令中。在imul 指令中,两个64位的乘法最多会产生128位的结果,需要 %rax 与 %rdx 共同存储乘法结果,在div 指令中被除数是128 位的,同样需要%rax 与 %rdx 共同存储被除数。 1. %rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。 1. %rbp 是栈帧指针,用于标识当前栈帧的起始位置 1. %rdi, %rsi, %rdx, %rcx,%r8, %r9 六个寄存器用于存储函数调用时的6个参数(如果有6个或6个以上参数的话)。 1. 被标识为 “miscellaneous registers” 的寄存器,属于通用性更为广泛的寄存器,编译器或汇编程序可以根据需要存储任何数据。 转自:[x86-64 下函数调用及栈帧原理](https://zhuanlan.zhihu.com/p/27339191) ## 3 extern关键字与头文件之间的区别和联系 [extern 与头文件(*.h)的区别和联系](https://www.runoob.com/w3cnote/extern-head-h-different.html) ## 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)的值 ### 整数 缩小范围 截断:直接裁掉 ## 问题 1. 感觉看了半天 最大的感受就是“永远不要用unsigned”,不过JeffQean说OI里经常有必要要用,这么一想,oi确实……时空的较量。 最后修改:2021 年 09 月 02 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得这篇文章对你有用,请随意赞赏~