cmp指令、jne指令 与 flags寄存器
-
flags寄存器(标记寄存器):CPU 中专门用于存储 逻辑判断 结果的寄存器。不同型号的 CPU,flags寄存器 的位数可能不同(8bit、16bit、32bit、64bit),每种型号的 CPU 都会提供一张 结果状态表,用于说明 flags寄存器 每个bit 所代表的含义。
-
cmp(compare)指令:用于比较两个操作数的大小,并将结果状态记录到 flags寄存器 中。cmp指令 通过对两个操作数做减法,来实现比较功能。
-
jne(jump not equal)指令:判断 flags寄存器 中的对应 bit 的状态,来决定是否跳转到指定的标号处执行指令(一般是当 ZF== 0,即 ZF 为假,转至标号处执行)。
-
在 Objective-C 中编写的判断语句如下
int main(int argc, const char * argv[]) { int a = 10; if (a == 9) { NSLog(@"a == 9"); } else if (a == 10) { NSLog(@"a == 10"); } else if (a == 11) { NSLog(@"a == 11"); } else { NSLog(@"unknow"); } return 0; }
-
其对应的汇编代码如下
Test`main: 0x100000ed0 <+0>: pushq %rbp 0x100000ed1 <+1>: movq %rsp, %rbp 0x100000ed4 <+4>: subq $0x20, %rsp 0x100000ed8 <+8>: movl $0x0, -0x4(%rbp) 0x100000edf <+15>: movl %edi, -0x8(%rbp) 0x100000ee2 <+18>: movq %rsi, -0x10(%rbp) 0x100000ee6 <+22>: movl $0xa, -0x14(%rbp) ; int a = 10 0x100000eed <+29>: cmpl $0x9, -0x14(%rbp) ; if (a == 9) 0x100000ef1 <+33>: jne 0x100000f0d ; <+61> at main.m:16:18,如果 a != 9,则跳转到 0x100000f0d(即<+61>)处执行指令 0x100000ef7 <+39>: leaq 0x10a(%rip), %rax ; @"a == 9" 0x100000efe <+46>: movq %rax, %rdi 0x100000f01 <+49>: movb $0x0, %al 0x100000f03 <+51>: callq 0x100000f70 ; symbol stub for: NSLog 0x100000f08 <+56>: jmp 0x100000f68 ; <+152> at main.m 0x100000f0d <+61>: cmpl $0xa, -0x14(%rbp) ; if (a == 10) 0x100000f11 <+65>: jne 0x100000f2d ; <+93> at main.m:18:18,如果 a != 10,则跳转到 0x0x100000f2d(即<+93>)处执行指令 0x100000f17 <+71>: leaq 0x10a(%rip), %rax ; @"a == 10" 0x100000f1e <+78>: movq %rax, %rdi 0x100000f21 <+81>: movb $0x0, %al 0x100000f23 <+83>: callq 0x100000f70 ; symbol stub for: NSLog 0x100000f28 <+88>: jmp 0x100000f63 ; <+147> at main.m 0x100000f2d <+93>: cmpl $0xb, -0x14(%rbp) ; if (a == 11) 0x100000f31 <+97>: jne 0x100000f4d ; <+125> at main.m,如果 a != 11,则跳转到 0x100000f4d(即<+125>)处执行指令 0x100000f37 <+103>: leaq 0x10a(%rip), %rax ; @"a == 11" 0x100000f3e <+110>: movq %rax, %rdi 0x100000f41 <+113>: movb $0x0, %al 0x100000f43 <+115>: callq 0x100000f70 ; symbol stub for: NSLog 0x100000f48 <+120>: jmp 0x100000f5e ; <+142> at main.m 0x100000f4d <+125>: leaq 0x114(%rip), %rax ; @"unknow" 0x100000f54 <+132>: movq %rax, %rdi 0x100000f57 <+135>: movb $0x0, %al 0x100000f59 <+137>: callq 0x100000f70 ; symbol stub for: NSLog 0x100000f5e <+142>: jmp 0x100000f63 ; <+147> at main.m 0x100000f63 <+147>: jmp 0x100000f68 ; <+152> at main.m 0x100000f68 <+152>: xorl %eax, %eax 0x100000f6a <+154>: addq $0x20, %rsp 0x100000f6e <+158>: popq %rbp 0x100000f6f <+159>: retq
-
汇编指令执行过程中,不同情况下,flags寄存器 对应的值如下(这里只是做一个展示,表明 cmp指令 执行前后,会改变 flags寄存器 中的值,flags寄存器 中每一个 bit 的具体含义,需要查 CPU 的结果状态表 )
// 在执行比较前,打印 rflags 寄存器的值 (lldb) po/x $rflags 0x0000000000000206 // 在执行比较后(条件不成立),打印 rflags 寄存器的值 (lldb) po/x $rflags 0x0000000000000202 // 在执行比较后(条件成立),打印 rflags 寄存器的值 (lldb) po/x $rflags 0x0000000000000246
数据结构简介
-
什么是数据结构
数据结构是计算机存储以及组织数据的方式。也可以理解为,有一堆数据,他们之间有些特殊的关系。 -
常见的数据结构(逻辑上)
- 线性表(数组、链表、栈、队列)
- 树
- 图
-
逻辑结构
数据结构从逻辑上可以分成下面几种结构:- 集合结构:集合结构里面有很多元素,但是这些元素之间是没有关系的,类似于 Objective-C 里面的 NSSet、NSMutableSet。
- 线性结构:线性结构是有顺序的,比如 Objective-C 中的 NSArray、NSMutableArray。
- 树状结构:树状结构是一个或多个节点的有限集合。
如下图所示:A 为根节点。D 是 I、J 的父节点。I、J 是 D 的子节点。I、J 为兄弟节点。
- 图形结构:图形结构简称图,是一种相对复杂的数据结构,任意两个节点之间都可以关联。
- 集合结构:集合结构里面有很多元素,但是这些元素之间是没有关系的,类似于 Objective-C 里面的 NSSet、NSMutableSet。
-
存储结构
数据结构从存储上可以分成下面几种结构:- 顺序存储结构:这组存储单元的内存地址是连续的。适用于:对数据遍历和查找频繁 && 对数据插入和删除较少 的场合。
- 链式存储结构:这组存储单元的内存地址可以是连续的也可以是不连续的,它不要求逻辑上相邻的元素在物理地址上也相邻。适用于:对数据遍历和查找较少 && 对数据插入和删除频繁 的场合。
- 顺序存储结构:这组存储单元的内存地址是连续的。适用于:对数据遍历和查找频繁 && 对数据插入和删除较少 的场合。
-
线性表
- 线性表就是多个具有相同特性的数据元素(或节点)组成的,有限而且有序的集合(这里是从逻辑上描述线性表)。
- 当线性表的节点个数为 0 时,称之为空表。
- 线性表第一个元素称为首节点,最后一个节点称为尾节点。
- 比如某个线性表的元素:a0、a1、a2、…、a99。那么 a0、…、a98 都是 a99 的前驱,a98 是 a99 的直接前驱。
- 比如某个线性表的元素:a0、a1、a2、…、a99。那么 a1、…、a99 都是 a0 的后继,a1 是 a0 的直接后继。
线性表的顺序存储结构:用一组地址连续的存储单元依次存储线性表的数据元素
线性表的链式存储结构:用一组任意的存储单元存储线性表中的数据元素,它的存储单元可以是连续的,也可以是不连续的。
用 C语言 写一个 Objective-C 的数组对象(顺序存储结构)
-
栈(Stack)与堆(Heap)
- 栈空间用于存储函数调用过程中的方法调用、参数、局部变量,所需空间由系统自动分配,回收也由系统管理,无需人工干预
堆空间用于存储动态分配的数据,分配和释放均由程序员控制,有可能产生内存泄漏 - 栈空间作为一个严格后进先出的数据结构,可用空间永远都是一块连续的内存区域
堆空间在不断分配和释放的过程中,可用空间链表频繁更新,造成可用空间逐渐碎片化,每块可用内存空间都很小 - 栈空间的默认大小只有几 MB,生长方式是向上的,也就是向着内存地址减小的方向消耗内存空间
堆空间的理论大小有几 GB,生长方式是向下的,也就是向着内存地址增大的方向消耗内存空间 - 栈空间有计算机底层的支持,压栈和出栈都有专门的汇编指令,效率较高
堆空间通过函数动态开辟,涉及可用空间链表的扫描和调整以及相邻可用空间的合并等操作,效率相对较低
通过 malloc 函数向操作系统申请堆区的内存空间
通过 free 函数通知操作系统销毁之前申请的堆区的内存空间
Objective-C 的 alloc 方法底层,调用的是 C语言的 malloc 函数 - 栈空间用于存储函数调用过程中的方法调用、参数、局部变量,所需空间由系统自动分配,回收也由系统管理,无需人工干预
-
Objective-C 中的指针
- Objective-C 中任何类型的指针,都占用 8Byte 的内存空间
- int* 类型的指针,只能指向 int 类型的数据,指针的步长为 4Byte
long* 类型的指针,只能指向 long 型的数据,指针的步长为 8Byte
void* 类型的指针,为万能指针,能指向任意类型的数据 - Objective-C 中,给指针赋空值,使用 NULL,不是使用 nil
-
分析 Objective-C 中 NSMutableArray 的属性与方法,规划线性表对象
1.属性方面:容量(capacity)、长度(length)、指向存储线性表元素的堆内存的指针(value)
2.方法方面:创建线性表、销毁线性表、清空线性表、获取线性表的属性(capacity、length)、对线性表的增、删、改、查、删除特定值,打印线性表对象内存图如下所示:
-
C语言 创建数组对象方法 listCreat() 分析
写法一:listCreat() 函数,直接返回 表示数组对象的结构体
1.这样写不好的地方在于:
结构体的赋值为深拷贝,在 main 函数中调用 listCreat() 函数后,会再生成一份与 listCreat() 函数内部创建的结构体一模一样的结构体给 main 函数。如果结构体成员很多,会造成内存空间的浪费。
2.优化思路:
listCreat() 函数中返回指向结构体的指针,无论结构体的成员有多少个,都只返回 8Byte 的内存地址,从而节省了内存空间。// ---------------------------- main.m ---------------------------- #import <Foundation/Foundation.h> #import "HCGLinearList.h" int main(int argc, const char * argv[]) { @autoreleasepool { LinearList list = listCreat(10); // 输出结果: // list size = 16 NSLog(@"list size = %ld", sizeof(list)); } return 0; } // ---------------------------- HCGLinearList.h ---------------------------- #ifndef LinearList_h #define LinearList_h // 定义节点数据的指针 typedef int* LinearListNodeValue; // 线性表结构体 typedef struct { int capacity; // 容量 int length; // 长度 LinearListNodeValue value; // 节点数据的指针 } LinearList; // 用于创建线性表的函数 // param0.线性表容量 // return.线性表结构体 LinearList listCreat(int capacity); #endif /* LinearList_h */ // ---------------------------- HCGLinearList.c ---------------------------- #include "HCGLinearList.h" // 用于创建线性表的函数 LinearList listCreat(int capacity) { LinearList list; list.capacity = capacity; list.length = 0; list.value = NULL; return list; }
写法二:listCreat() 函数,直接在内部创建一个结构体,然后返回指向该结构体的指针
1.这样写不好的地方在于:如果直接在 listCreat() 函数内部,创建一个结构体,这个结构体相当于 listCreat() 函数的局部变量,此时结构体保存在 listCreat() 函数的栈空间里面。联系前面所学的汇编知识,listCreat() 函数的栈空间,在 istCreat() 函数执行完毕之后,就被释放了。
2.优化思路:在 listCreat() 函数内部,通过 malloc 函数,在堆区申请内存,存储创建的结构体,并返回指向存储在堆区的结构体的指针// ---------------------------- main.m ---------------------------- #import <Foundation/Foundation.h> #import "HCGLinearList.h" int main(int argc, const char * argv[]) { @autoreleasepool { LinearList* list0 = listCreat(10); LinearList* list1 = listCreat(10); LinearList* list2 = listCreat(10); // 输出结果:(注意,因为函数调用平栈的关系,list1 和 list2 的内存地址完全一样) // list0 = 0x7ffeefbff558, list1 = 0x10052e620, list2 = 0x10052e620 NSLog(@"list0 = %p, list1 = %p, list2 = %p", &list0, list1, list2); } return 0; } // ---------------------------- HCGLinearList.h ---------------------------- #ifndef LinearList_h #define LinearList_h // 定义节点数据的指针 typedef int* LinearListNodeValue; // 线性表结构体 typedef struct { int capacity; // 容量 int length; // 长度 LinearListNodeValue value; // 节点数据的指针 } LinearList; // 用于创建线性表的函数 // param0.线性表容量 // return.指向 线性表结构体 的指针 LinearList* listCreat(int capacity); #endif /* LinearList_h */ // ---------------------------- HCGLinearList.c ---------------------------- #include "HCGLinearList.h" // 用于创建线性表的函数 LinearList* listCreat(int capacity) { // 分配线性表结构体的内存空间(在本函数的栈区) LinearList list; list.capacity = capacity; list.length = 0; list.value = NULL; LinearList* pList = &list; return pList;; }
写法三:在 listCreat() 函数内部,通过 malloc 函数,在堆区申请内存,存储创建的结构体,并返回指向存储在堆区的结构体的指针
// ---------------------------- main.m ---------------------------- #import <Foundation/Foundation.h> #import "HCGLinearList.h" int main(int argc, const char * argv[]) { @autoreleasepool { LinearList* list0 = listCreat(10); LinearList* list1 = listCreat(10); LinearList* list2 = listCreat(10); // 输出结果: // list0 = 0x7ffeefbff558, list1 = 0x10052e620, list2 = 0x100527070 NSLog(@"list0 = %p, list1 = %p, list2 = %p", &list0, list1, list2); } return 0; } // ---------------------------- HCGLinearList.h ---------------------------- #ifndef LinearList_h #define LinearList_h typedef int* LinearListNodeValue; // 线性表结构体 typedef struct { int capacity; // 容量 int length; // 长度 LinearListNodeValue value; // 节点数据的指针 } LinearList; // 用于创建线性表的函数 // param0.线性表容量 LinearList* listCreat(int capacity); #endif /* LinearList_h */ // ---------------------------- HCGLinearList.c ---------------------------- #include "HCGLinearList.h" // 使用 mallc函数 需要导入标准库 #include <stdlib.h> // 用于创建线性表的函数 LinearList* listCreat(int capacity) { // 分配线性表结构体的内存空间(在堆区) LinearList* list = malloc(sizeof(LinearList)); // malloc 函数如果遇到内存资源紧张,给不了这么多字节,可能会返回空 if (list) { list->capacity = capacity; list->length = 0; // 分配真正存储线性表中元素的内存空间 list->value = malloc(capacity * sizeof(LinearListNodeValue)); // 对存储元素的内存空间进行初始化 for (int i = 0; i < capacity; i++) { list->value[i] = 0x11111111; } } return list;; }
HCGLinearList 代码(未封装)
-
HCGLinearList.h 文件如下
#ifndef LinearList_h #define LinearList_h #include <stdio.h> #pragma mark - 定义线性表结构体 // 重命名 int 类型 typedef int LinearListNodeValue; // 线性表结构体 typedef struct { int capacity; // 容量 int length; // 长度 LinearListNodeValue* value; // 节点数据的指针 } LinearList; #pragma mark - 创建 销毁 清空 // 创建线性表 // param0.线性表容量 // return.线性表指针 LinearList* listCreat(int capacity); // 销毁线性表 // param0.线性表指针 void listRelease(LinearList* list); // 清空线性表 // param0.线性表指针 void listClear(LinearList* list); #pragma mark - 属性获取 // 获取线性表的长度 // param0.线性表指针 // return.线性表长度 int listLength(LinearList* list); // 获取线性表容量 // param0.线性表指针 // return.线性表容量 int listCapacity(LinearList* list); #pragma mark - 增 // 往线性表中插入数据 // param0.线性表指针 // param1.要插入的位置的索引 // param2.要插入的值 void listInsert(LinearList* list, int index, LinearListNodeValue value); // 往线性表中添加数据(添加在表尾) void listAdd(LinearList* list, LinearListNodeValue value); #pragma mark - 删 // 删除线性表中指定索引位置的元素 // param0.线性表指针 // param1.索引 void listRemove(LinearList* list, int index); #pragma mark - 改 // 修改线性表中指定位置的元素为指定的值 // param0.线性表指针 // param1.索引 // param2.值 void listSet(LinearList* list, int index, LinearListNodeValue value); #pragma mark - 查 // 获取线性表指定索引处元素的值 // param0.线性表指针 // param1.索引 // return.元素的值 LinearListNodeValue listGet(LinearList* list, int index); #pragma mark - 特殊功能 // 删除线性表中具有指定值的所有元素 // param0.线性表指针 // param1.要删除的值 void listRemoveValue_1(LinearList* list, LinearListNodeValue value); // 删除线性表中具有指定值的所有元素 // param0.线性表指针 // param1.要删除的值 void listRemoveValue_2(LinearList* list, LinearListNodeValue value); // 打印线性表 // param0.线性表指针 void listPrint(LinearList* list); #endif /* LinearList_h */
-
HCGLinearList.c 文件如下
#include "HCGLinearList.h" // 使用 mallc函数 需要导入 #include <stdlib.h> #pragma mark - 创建 销毁 清空 // 创建线性表 LinearList* listCreat(int capacity) { if (capacity < 0) { return NULL; } // 分配线性表结构体的内存空间(在堆区) // malloc函数如果遇到内存资源紧张,给不了这么多字节,可能会返回空 LinearList* list = malloc(sizeof(LinearList)); if (list) { list->capacity = capacity; list->length = 0; // 分配存储线性表元素的内存空间 list->value = malloc(capacity * sizeof(LinearListNodeValue)); if (!list->value) { return NULL; } } return list;; } // 销毁线性表 void listRelease(LinearList* list) { if (NULL == list) { return; } if (list->value) { free(list->value); } free(list); } // 清空线性表 void listClear(LinearList* list) { if (NULL == list) { return; } // 这里不需要对线性表中的元素都置0 // 只要将线性表的长度置为0,下次使用线性表的时候,就会对之前的数据进行覆盖了 list->length = 0; } #pragma mark - 属性获取 // 获取线性表的长度 int listLength(LinearList* list) { if (NULL == list) { return 0; } return list->length; } // 获取线性表容量 int listCapacity(LinearList* list) { if (NULL == list) { return 0; } return list->capacity; } #pragma mark - 增 // 往线性表中插入数据 void listInsert(LinearList* list, int index, LinearListNodeValue value) { if (NULL == list) { return; } // 可以在表尾进行插入,因此这里的条件是 index > list->length,而不是 index >= list->length if (index < 0 || index > list->length) { return; } if (list->length == list->capacity) { return; } // 反向for循环挪动数据:从表尾数据开始挪动直到index标识的位置,每个数据依次向后挪动一个步长 for (int i = list->length - 1; i >= index; i--) { list->value[i + 1] = list->value[i]; } // 将新值value插入到index的位置 list->value[index] = value; // 线性表的长度 + 1 list->length++; } // 往线性表中添加数据(添加在表尾) void listAdd(LinearList* list, LinearListNodeValue value) { if (NULL == list) { return; } listInsert(list, list->length, value); } #pragma mark - 删 // 删除线性表中指定索引位置的元素 void listRemove(LinearList* list, int index) { if (NULL == list) { return; } if (index < 0 || index > list->length - 1) { return; } // 反向for循环挪动数据:从(index + 1)标识的位置开始直到表尾,每个数据依次向前挪动一个步长 for (int i = index + 1; i < list->length; i++) { list->value[i - 1] = list->value[i]; } /* // 等效写法 for (int i = index; i < list->length - 1; i++) { list->value[i] = list->value[i + 1]; } */ // 线性表的长度 - 1 list->length--; } #pragma mark - 改 // 修改线性表中指定位置的元素为指定的值 void listSet(LinearList* list, int index, LinearListNodeValue value) { if (NULL == list) { return; } if (index < 0 || index > list->length - 1) { return; } list->value[index] = value; } #pragma mark - 查 // 获取线性表指定索引处元素的值 LinearListNodeValue listGet(LinearList* list, int index) { if (NULL == list) { return 0; } if (index < 0 || index > list->length - 1) { return 0; } return list->value[index]; } #pragma mark - 特殊功能 // 删除线性表中具有指定值的所有元素 - 代码简单,但是效率低(时间复杂度高) void listRemoveValue_1(LinearList* list, LinearListNodeValue value) { if (NULL == list) { return; } // 遍历所有元素 for (int i = 0; i <= list->length - 1; i++) { while (list->value[i] == value && i <= list->length - 1) { listRemove(list, i); } } } // 删除线性表中具有指定值的所有元素 - 效率较高 void listRemoveValue_2(LinearList* list, LinearListNodeValue value) { if (NULL == list) { return; } // 遍历所有元素 int removeCount = 0; for (int i = 0; i <= list->length - 1; i++) { if (list->value[i] == value) { removeCount++; } else { list->value[i - removeCount] = list->value[i]; } } // 将长度减去删除的个数 list->length -= removeCount; } // 打印线性表 void listPrint(LinearList* list) { if (NULL == list) { return; } printf("list{\n"); printf("\tlength = %d;\n", list->length); printf("\tcapacity = %d;\n", list->capacity); printf("\tvalue = ["); for (int i = 0; i <= list->length - 1; i++) { printf("%d", list->value[i]); if (i <= list->length - 2) { printf(","); } } printf("];\n\t}\n\n"); }
-
在 main.m 中简单测试如下
#import <Foundation/Foundation.h> #import "HCGLinearList.h" int main(int argc, const char * argv[]) { @autoreleasepool { // 创建 LinearList LinearList* list = listCreat(10); // 添加数据 listAdd(list, 10); listAdd(list, 10); listAdd(list, 10); listAdd(list, 66); listAdd(list, 66); // 插入数据 listInsert(list, 1, 20); listInsert(list, 2, 30); // 打印 1 listPrint(list); // 修改数据 listSet(list, 3, 40); // 删除数据 listRemove(list, listLength(list) - 1); // 打印 2 listPrint(list); // 移除所有为值 10 的数据 listRemoveValue_2(list, 10); // 打印 3 listPrint(list); // 销毁 LinearList listRelease(list); list = NULL; } return 0; }
测试结果如下
// 打印 1 list{ length = 7; capacity = 10; value = [10,20,30,10,10,66,66]; } // 打印 2 list{ length = 6; capacity = 10; value = [10,20,30,40,10,66]; } // 打印3 list{ length = 4; capacity = 10; value = [20,30,40,66]; }
注意
-
凡是指针类型定义出来的变量,里面保存的值,都会被当做地址值来处理。
int a = 10; int b = 11; int* p0 = &a; int* p1 = &b; *p0 = 0; *p1 = 1; printf("a = %d, b = %d", a, b); // 输出结果 // a = 0, b = 1 // 变量 p2 里面存储的是 p0 指针往下移动 sizeof(int) * 1 个 Byte 处的地址 int* p2 = p0 + 1; // 变量 p3 里面存储的是 p1 指针往上移动 sizeof(int) * 1 个 Byte 出的地址 int* p3 = p1 - 1;
-
void 有空的意思,也有无类型的意思,因此 void* 的指针不能 + 或 -