代码随想录 + 嵌入式八股文 (5)

反转链表内存篇

1.内存的分配方式:

        ① 栈上分配:

              在执行函数之前,函数内部的局部变量都可以在栈上创建,函数执行完毕之后会自动释放

        ②静态全局存储区        全局变量和静态变量

        ③堆上分配                   由程序员分配,好比new,free,malloc free

2.堆和栈有什么区别(申请方式、效率、方向)

       ①.申请方式不同:

                栈是由操作系统自由分配和释放,堆是由程序员手动申请释放

        ②.申请大小的限制:

                栈是向低地址申请的空间,一块连续的内存,也就是说,栈的大小和地址是由系统预先规定好的,如果申请的空间超过栈的剩余空间就会提示overflow

                堆的申请是向高地址扩张的,是不连续的内存区域,这是由于系统使用链表来存储的空闲内存地址

        ③.申请的效率   

                栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即   

                释放;堆则是存放在二级缓存中,速度要慢些

堆栈溢出一般是由什么原因导致的?(递归,动态申请内存,数组访问越界,指针非法访问)

答: ①.函数调用层次太深。函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。

        ②.动态申请空间使用之后没有释放。由于C语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。申请的动态空间使用的是堆空间,动态空间使用不当造成堆溢出。

        ③.数组访问越界。C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。

        ④.指针非法访问。指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。

3.栈在c语言中有什么作用(另外栈还是数据结构的一种)

    1.用来保存临时变量,临时变量包括函数参数,函数内部定义的临时变量

    2.多线程编程的基础就是栈,栈是多线程编程的基石,每个线程多最少有自己的专属的栈,用来保存本线程运行时各个函数的临时变量

4.c++的内存管理是怎样的

        在c++中虚拟内存分为代码段、数据段(bss段,data段)、堆、栈、共享区

代码段:   包括只读存储去和文本区,其中只读存储区字符串常量,文本区存储程序的机械代码

数据段:全局变量、静态变量(全局、局部)

        BSS段:未初始化的全局变量和静态变量(全局、局部),以及所有被初始化为0的全局变量和静态变量

        data段:初始化的全局变量和静态变量(全局,局部)

堆:调用new/malloc申请的内存空间,地址由低地址向高地址扩张

栈:局部变量、函数的返回值,函数的参数,地址由高地址向低地址扩张

映射区:存储动态链接库以及调用mmap函数的文件映射

5什么是内存泄漏

        简单来说就是申请了内存,不使用之后并没有释放内存,或者说,指向申请的内存的指针突然又去指向别的地方,导致找不到申请的内存,有什么影响:随着程序运行时间越长,占用内存越多,最终用完内存,导致系统崩溃。

        内存泄漏是指动态申请内存,1)但是不用之后内存没有释放掉,一直留在程序里,2)或者申请了内存,指针又指向了别的地方

        1)不用之后内存没有释放掉,一直留在程序里浪费

        2)申请了内存,指针又指向了别的地方

char *p = (char*)malloc(sizeof(int)*25);

      if( p == NULL)

      {

                return -1;

       }

        printf("malloc %p\n",p);  //malloc 0x5573503ee2a0

        p = "hello";

        printf("hello %p\n",p);  //hello 0x55734f5eb00f

//可以看出这里出现内存泄漏:

//原本p指向使用malloc申请内存的地址0x5573503ee2a0,后来又让p指向字符串常量的地址0x55734f5eb00f,此时就找不到malloc的地址了

6如何判断内存泄漏(如何减少内存泄漏)

        1.良好的编码习惯,使用内存分配的函数,一但使用完毕之后就要记得使用对应的函数是否掉

        2.将分配的内存的指针以链表的形式自行管理,使用之后从链表中删除,程序结束时可以检查改链表

        3.使用智能指针

        4.使用常见插件,ccmalloc

7.什么是内存溢出 (out of memory)  异常(OutOfMemoryError  StackOverflowError)

           指程序要求的内存超出了系统所能分配的范围,出现out of memory;比如申请一个int类型,但给了它一个int才能存放的数,就会出现内存溢出,或者是创建一个大的对象,而堆内存放不下这个对象,这也是内存溢出。

8字节对齐问题

        什么是字节对齐:需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。常见就是求复合类型大小,比如结构体、联合体  。

为什么需要字节对齐 ?

        需要字节对齐的根本原因在于CPU访问数据的效率问题!

例如 : 如果0x02~0x05存了一个int,读取这个int就需要先读0x01~0x04,留下0x02~0x04的内容,再读0x05~0x08,留下0x05的内容,两部分拼接起来才能得到那个int的值,这样读一个int就要两次内存访问,效率就低了。

内存对齐作用: 1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某 些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存, 处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

9.C语言函数参数压栈顺序是怎样的

        从右往左,并且内存中栈是由高向低扩展,所以先入栈的是右边并且地址是高位比如printf()函数,也都是先打印最右边。

10.栈的空间值最大为多少

Window是2MB,Linux是8MB

使用ulimit -s 查看

11.在1G内存的计算机能否malloc(1.2G)?为什么?

有可能,应用程序通过malloc函数可以向程序的虚拟空间 申请一块虚拟地址空间,与物理内存没有直接关系,得到的是在虚拟地址空间中的地址,之后程序运行 所提供的物理内存是由操作系统完成的。

12strcat、strncat、strcmp、strcpy哪些函数会导致内存溢出?如何改进?

strcpy函数会导致内存溢出。

strcpy拷贝函数不安全,他不做任何的检查措施,也不判断拷贝大小,不判断目的地址内存是否够用。

strncpy拷贝函数,虽然计算了复制的大小,但是也不安全,没有检查目标的边界。

strncpy_s是安全的 strcmp(str1,str2),是比较函数,若str1=str2,则返回零;若str1str2,则 返回正数。(比较字符串)

strncat()主要功能是在字符串的结尾追加n个字符。 strcat()函数主要用来将两个char类型连接。

13.malloc 、calloc、realloc内存申请函数

申请堆内存

       void *malloc(size_t size);         //申请size_t个字节内存

       void free(void *ptr);                 //释放内存,但是指针还是可以用

       void *calloc(size_t nmemb, size_t size);         //申请nmemb快内存,每块size_t个字节

       void *realloc(void *ptr, size_t size);                //申请内存,重新申请size_t字节内存,

       void *reallocarray(void *ptr, size_t nmemb, size_t size);

说明:

        1.malloc()

内存未初始化,如果size为0,则malloc()返回NULL或一个稍后可以成功传递给free          ()的唯一指针值。

        2.Realloc()

如果size_t>原来的s申请的空间大小,比如原来是100个字节,现在是150个字节,那么就有以下两种情况:

  1. 原来100个字节后面还能放的下50个字节,那么就在原来地址上增加50个字节,返回的还是原来地址
  2. 如果100个字节后面放不下50个字节,那么就会重新找个地址开辟150个字节空间,把原来的地址数据拷贝过来,释放掉原来地址空间,返回一个新的地址
  3. 如果size_t<原来的s申请的空间大小,比如原来是100个字节,现在是50个字节,那么就会释放掉后50个字节

  4. calloc申请内存空间后,会自动初始化内存空间为 0

代码随想录

 

 从逻辑上理解,匹配从相同长度开始

 大神解法

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if (headA == NULL || headB == NULL) {
        return NULL;
    }
    struct ListNode *pA = headA, *pB = headB;
    while (pA != pB) {
        pA = pA == NULL ? headB : pA->next;
        pB = pB == NULL ? headA : pB->next;
    }
    return pA;
}

 

如果有环,如何找到这个环的入口:引用代码随想录

代码随想录

 假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

(x + y) * 2 = x + y + n (y + z) 

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

当 n为1的时候,公式就化解为 x = z从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

链表总结:

1.链表的理论基础

  • 链表的种类主要为:单链表,双链表,循环链表
  • 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
  • 链表是如何进行增删改查的。
  • 数组和链表在不同场景下的性能分析

2链表经典题目

虚拟头结点:

        链表操作中一个非常总要的技巧:虚拟头节点。每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题

链表的基本操作

设计链表中链表常见的五个操作

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点的数值

反转链表

        

删除倒数第N个节点

        结合虚拟头结点 和 双指针法来移除链表倒数第N个节点

链表相交

        使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点)

环形链表

        在链表如何找环,以及如何找环的入口位置。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值