范围

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数据段文档

Linux段管理,BSS段,data段,.rodata段,text段

Data segment - Wikipedia

栈指针与帧指针 函数调用

帧指针使得访问函数的参数很容易。所以任何函数调用进来的第一件事都是保护调用者的帧指针,以使得返回时可以恢复调用者的帧指针,

pushl %ebp

movl %esp %ebp

有了上面这两个命令,函数就可返回了,返回时只要

leave

movl %ebp &esp

popl %ebp

————————————————

版权声明:此段为CSDN博主「Wanderer001」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:栈指针&& 帧指针详解

图片转自这篇文章:x86-64 下函数调用及栈帧原理

上面这个链接详细讲述了函数调用的栈帧原理,是顶级好文明,建议通读。

x86-64常见寄存器说明

16个通用寄存器:

  1. 每个寄存器的用途并不是单一的。
  2. %rax 通常用于存储函数调用的返回结果,同时也用于乘法和除法指令中。在imul 指令中,两个64位的乘法最多会产生128位的结果,需要 %rax 与 %rdx 共同存储乘法结果,在div 指令中被除数是128 位的,同样需要%rax 与 %rdx 共同存储被除数。
  3. %rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。
  4. %rbp 是栈帧指针,用于标识当前栈帧的起始位置
  5. %rdi, %rsi, %rdx, %rcx,%r8, %r9 六个寄存器用于存储函数调用时的6个参数(如果有6个或6个以上参数的话)。
  6. 被标识为 “miscellaneous registers” 的寄存器,属于通用性更为广泛的寄存器,编译器或汇编程序可以根据需要存储任何数据。

转自:x86-64 下函数调用及栈帧原理

3 extern关键字与头文件之间的区别和联系

extern 与头文件(*.h)的区别和联系

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. 12345U0x1A2bu

整数 扩大范围

无符号数扩大范围时直接左边补0

有符号数扩大范围时左边补原数据最高有效位(bit)的值

整数 缩小范围

截断:直接裁掉

问题

  1. 感觉看了半天 最大的感受就是“永远不要用unsigned”,不过JeffQean说OI里经常有必要要用,这么一想,oi确实……时空的较量。
最后修改:2021 年 09 月 02 日 04 : 13 PM
如果觉得这篇文章对你有用,请随意赞赏~