zoukankan      html  css  js  c++  java
  • 你的C/C++程序为什么无法运行?揭秘Segmentation fault (1)

    什么让你对C/C++如此恐惧?

    晦涩的语法?还是优秀IDE的欠缺?
    我想那都不是问题,最多的可能是一个类似这样的错误:

    这里写图片描述

    段错误(Segmentation fault)

    这是新手无法避免的错误,也是老手极力回避也经常遇到的错误。
    本篇,试图简略地剖析一段会引发这个错误的程序,带来一些启发。

    先看两份代码,一份是错误的.

    错误代码

    #include "string.h"
    #include <stdlib.h>
    #include <stdio.h>
    
    void func1(char ** dest,char * src,int n) {
        (*dest) = (char*)malloc(sizeof(char)*n);
        strcpy(*dest,src);
    
    }
    
    int main(int argc,char** args) {
        char ** p = NULL;
        char str[] = "foreach_break";
        int len = sizeof(str);
        printf("%d
    ",len); 
        func1(p,str,len);   
        printf("%s
    ",*p);
        free(p);
        p = NULL;
    }

    正确代码

    #include "stdio.h"
    #include "string.h"
    #include "stdlib.h"
    
    
    void func1(char ** dest,char * src,int n) {
        (*dest) = (char*)malloc(sizeof(char)*n);
        strcpy(*dest,src);
    
    }
    
    int main(int argc,char** args) {
        char * p = NULL;
        char str[] = "foreach_break";
        int len = sizeof(str);
        printf("%d
    ",len);
        func1(&p,str,len);
        printf("%s
    ",p);
        free(p);
        //p = NULL;
    }

    代码意图来自技术问答中的一个huffman树不能运行的问题。
    当然,我剥离掉了大部分关于huffman的部分,并稍加改动。

    它们最大的不同:
    错误代码:

    char ** p = NULL;
    func1(p,str,len);

    正确代码:

    char * p = NULL;
    func1(&p,str,len);

    也许你会奇怪,你看到“正确”的代码中居然注释了这行:

    //p = NULL;

    参数传递

    同时,可能有人会觉得指针char ** p向函数func1传递*p,与指针char * p向函数func1传递&p没什么不同啊?

    嗯。这种想法也很有惯性,因为C语言没有类似这样的函数声明:
    void func(int &)

    同时,对char * p&p操作不也得到个char **吗?

    运行程序

    那么,我们看看程序自己怎么说?
    这里写图片描述

    如果你经常遇到段错误,希望你仔细看明白上面的图在说什么。

    野指针

    所谓野指针,就是很野的指针,你不知道它指向了哪个地址,也不知道对这个地址取值是否会出错,但,野指针也是指针,有一个存放它的内存地址.

    正确的代码中,存放指针p的地址是0x7fffffffddc0;
    错误的代码中,存放指针p的地址是0x7fffffffdd78

    零指针

    所谓零指针,就是指向了0x0的指针.对这个0x0取值是否会出错呢?你想一想.

    这里写图片描述

    悬浮指针

    你对于在正确的程序中注释了p = NULL而感到不解?
    其实这没什么,取决于这个指针p在后续代码中怎么使用.

    free(p);

    这句代码的执行,会释放掉指针p所指向的内存地址,归还给操作系统.
    当然前提是这个地址确有所指、你也有权访问它.

    p = NULL;

    这句代码的执行,是让指针p指向了0x0,变回了空指针.

    虽然它指向的内存已经被释放,但是它还指向那个地址.

    这就是悬浮指针,指向的地址已经不可用确还指向,就不是确有所指.

    由于我们的main函数即将执行完毕,所以在它返回后,存放空指针p的地址会被释放.

    因为指针pmain函数的一个临时变量.

    所以我们可以毫无顾虑的注释掉p = NULL.

    内存泄露

    另外,如果free(p)没有被执行,而先执行了p = NULL,那么p原来指向的内存空间可能就无法被正确释放,如果再也没有其它的引用指向了那块地址,那块地址就被遗忘在那里,同时不能被回收.

    这个,叫内存泄露 (Memory Leak).

    接着看程序

    现在,我们来看看函数func1的调用。

    首先,是C代码:
    这里写图片描述

    然后是两段代码的对比:
    这里写图片描述

    你应该已经看出了差别。

    错误的代码的dest参数传入了0x0.

    接着是执行:

    (*dest) = (char*)malloc(sizeof(char)*n);

    这句代码在进行(*dest)时就会发生段错误.

    究其原因,就在于char ** p = NULLp变成了零指针,*p相当于对0x0这个地址取值.

    应用程序启动时,操作系统会建立一个进程(process),这个进程拥有自己独立的地址空间,称作虚拟地址空间(virtual memory space).

    0x0在这个空间中,不能被访问.

    试图访问一个不能被访问的空间,就会段错误.

    总结

    段错误的一种,我们探索完毕.
    现在你知道以下两种操作的含义了吗?

    /* p指向的地址,对其取值,如果这个地址有东西,也有权取,没问题.*/
    char ** p -> *p; (1)
    /* 存放p的地址,或者指向p的引用,这个地址必然有东西,所以没问题*/
    char * p -> &p   (2);

    本篇结束.

    【版权所有@foreach_break】 【博客地址 http://www.cnblogs.com/foreach-break】 可以转载,但必须注明出处并保持博客超链接
  • 相关阅读:
    Flutter 布局(九)- Flow、Table、Wrap详解
    Flutter 布局(三)- FittedBox、AspectRatio、ConstrainedBox详解
    广州小程序开发攻略
    Angular2 富文本编辑器 ng2-ckeditor 的使用
    onlyoffice5.4.2离线包下载—解决中文字体问题
    beego+vue父子组件通信(父子页面传值、父子组件传值、父子路由传值)
    beego+vue.js分离开发,结合发布,简单部署
    flow-vue.js移动端效果
    engineecms——工程师知识管理系统,带文档协作和状态和流程
    engineercms支持文档协作和文档流程,基于flow
  • 原文地址:https://www.cnblogs.com/foreach-break/p/4471224.html
Copyright © 2011-2022 走看看