关于C语言中函数调用和参数传递机制的探讨--汇编

揭示C语言函数调用的本质解析 C语言是面向过程的,而C++是面向对象的C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。 C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。 所以C与C++的最大区别在于它们... 阅读详情
函数,相信许多人也知道其重要性;一个文件往往由一个或者多个函数构成的。然而可能许多人还不知道函数调用的一些深层问题,所以我写的这篇文章一来是应了一个好朋友的要求而写,二来希望一些朋友能够从我这篇文章了解函数调用的机制。但是并不是每个人都可以完全读懂这文章,想完全读懂此文,我想必须具备三个条件:
一、对于C语言有一定的了解,最起码有一个整体的初步了解;
  二、能够读懂UNIX/LINUX下的AT&T语法的汇编;AT&T汇编与Intel汇编的差别还是挺大的;这个条件可能一些人就不具备了,但是你通过阅读此文相信也能对函数调用机制有一个大概的了解;
  三、看到这么长的文章,一定要有耐心,用心看相信应该多少有点帮助;
    好了,不讲废话了,进入主题吧。
一、基本知识框架了解:
    这部分主要讲一些基本的东西,主要是关于堆栈的知识。只有了解了堆栈的基础内容,才可以继续往下读。
1.概念性的知识:
    所谓堆栈,其实也就是程序使用的一种内存元素;它是内存中用来存放一些数据的区域。我曾经写过一篇文章发表在这个论坛上里面也谈到了堆和栈的区别;平常经常说的堆栈,其实也是栈,而不是堆,所以这里也一样。注意这和数据结构说的栈其实还是有区别的,不要混在一起。
2.堆栈的工作方式:    
    平常我们所说的数据是怎么存放在内存的?是从低地址开始,然后按照数据占用字节大小往高地址逐个存放的。但堆栈就不一样了。堆栈的工作方式是数据插入堆栈区域然后从堆栈区域删除数据。这是概括的说法。具体是这样的:    
    在UNIX/LINUX中,堆栈是从高地址向低地址衍生的。这里得说一个重要的东东,那就是堆栈指针ESP。堆栈指针是什么?它永远指向堆栈中的顶部(但如果按照地址值来说却是底部),是不是对顶部这个词的理解感觉有点模糊?就是说,比如说你压栈,就压进一个4字节的数据元素,那么ESP就向下移动了4个字节,注意这里是向下移动,所以ESP该指向了更低的地址,所以说它是指向了底部。你可以把堆栈想象成一个杯子,倒进水了水平线是不是上升了(这里把杯子最底端假设成高地址,把顶端设为低地址),倒出水了水平线是不是下降了?就和压栈和进栈的道理一样的。如果还没有理解也没关系,自己画个图仔细比较就可以了。这里让我偷懒一下就不画图了。    
3.压栈和进栈指令简介:
    压栈指令 :     pushx    source
    其中, 'x'可以是  'w'(表示字), 或者是'l'(表示长字);source可以是数值或者寄存器值或者内存地址;
    出栈指令 :     popx    des
    同样,'x'可以是  'w'(表示字), 或者是'l'(表示长字);des可以是寄存器值或内存值;
    关于最最基本的东西已经讲得差不多了,当然还有其他一些基本东西,留给大家去查资料了,这部分讲的都和本文有密切关系的东西。
二、函数如何通过堆栈来解决问题:
    这部分是对函数如何通过堆栈解决函数调用以及参数传递的理论性理解,相当重要,只有了解之后才可以进行实例的分析,这一大部分同样分成几个小部分:
1.通过堆栈操作实现参数的传递:  
前面说过,堆栈的基本操作可以是压栈和出栈,而参数的传递就是通过这种方式来实现的。ESP永远指向了堆栈顶部,如果这时候压进一个int型的数据元素,那么ESP向下移动了4个字节,这时候它还是指向了堆栈的顶部(注意了,顶部的地址比移动前的地址低,不要乱了)。假如把一个int型数据元素出栈,那么ESP向上移动4个字节,这时候它还是指向了堆栈的顶部,只是现在地址是增加了4个字节。所以,如果一个函数需要传递参数过去那么就得在调用函数之前先把参数压进栈,然后再调用。关于这点后面我会详细说一下,现在你如果没理解也没关系。
2.函数调用的一般汇编指令:
    函数调用的一般汇编指令都是那么几条,下面我把他们按一般顺序罗列出来:
    #Asm Code
    
    function:
            pushl    %ebp
            movl    %esp, %ebp
            subl    $8, %esp
            #...
            movl    %ebp, %esp
            popl    %ebp
            ret

    下面先简单分析这几句一般汇编指令的意思和目的。
    pushl    %ebp            #这句把寄存器%ebp压栈,目的是什么呢?看下一条指令:
    movl    %esp, %ebp      
#把寄存器%esp的值给了寄存器%ebp;想想前面说到的%esp寄存器是干什么的?用于指向堆栈的顶部,现在通过这条指令,%ebp都是指向了堆栈的顶部了;所以看看第一条指令,其实就是为了保护原来在%ebp寄存器中的内容#那么这里为什么又要把%esp的值赋给%ebp呢?这里的巧妙就来了。在函数的处理过程中,可能一些数据会被压进栈,那么这时候就会破坏栈里面原有的内容了,如果栈的内容被破坏了,指向栈顶的指针%esp指向的地址不准确了(不知道能不能用“不准确”这个词来形容,可能不太合适),那么到时候要清栈就会发生更多的意外问题了。清栈?先别管这个词,下面也会给出解释。所以第二条指令是为了保证有一个寄存器永远指向了栈顶而不必担心会出现刚才所说的问题。现在是寄存器%ebp永远指向栈顶了,而%esp可以移动而不必害怕数据会被破坏了。
    subl    $8, %esp      
#看这条指令,为什么无故要把%esp的值减去8呢?也就是说%esp向下移动8个字节,而这8个字节的空间到底用来干什么呢?这8个字节空间其实是为临时变量留出来的。注意,它会根据临时变量占用的字节大小而留出不同的空间大小,所以不一定是8个字节,可能是24或者36甚至更大的空间;不过临时变量太多不是好事情,这点注意。
    movl    %ebp, %esp        #这条指令把%ebp复制到%esp了,理由是什么?让%esp重新指向栈顶,这样就可以方便函数调用完毕后的清栈了。
    ret                        #函数调用完毕的返回指令,这句指令其实同时把函数调用刚刚开始压进的IP地址弹出栈。在下面会有详细分析。
    关于函数如何通过堆栈来解决问题的基本理论大概就说到这里,假如你对上面的内容不理解也没关系,下面第3个部分通过实例来分析可以让你有个比较深刻的理解。
三、函数调用和参数传递机制的实例分析:
    这是本文的实战分析部分了,通过例子来加深一下理解。我会先列出C代码出来,然后列出反汇编的汇编代码,结合C代码来分析汇编代码。我会尽可能对各种类型的函数调用或参数类型作一个分析,可能会显得比较累赘一点,不介意吧?准备好的话就开始吧:
1. 函数原型:void function(void);
    // C code
    
    void function(void)
    {
        return;
    }
    
    int main(void)
    {
        function();
        
        return 0;
    }

    反汇编一下看看汇编代码,下面是Linux 下的gcc反汇编后的代码(注意:是在我的机子上的反汇编代码):

    function:
        pushl   %ebp
        movl    %esp, %ebp
        popl    %ebp
        ret

    看看,因为函数function什么也没有做,所以直接就返回了,上面的指令和第2部分的代码基本上一样,甚至更简单,参照一下前面的分析:
    下面看看main函数的反汇编代码了,相对来说复杂一点,看好了:
    main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        addl    $15, %eax
        addl    $15, %eax
        shrl    $4, %eax
        sall    $4, %eax
        subl    %eax, %esp
        call    function        #函数调用指令
        movl    $0, %eax
        leave
        ret

    看看函数调用指令 : call    function,前面居然还有那么多据指令,那些指令到底干什么用?我一句一句分析吧:

    pushl    %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    
    这三句不分析,和前面第2部分的一样,忘记的回头看一下,其实这也反映了一件事:其实main函数也很普通,它跟其他函数其实差不多,只是地位稍微高一点而已。
    andl    $-16, %esp
    这句可能吓倒一些人了。 andl 是逻辑与指令,而-16其实补码形式是0xfffffff0。为什么要把%esp的值和-16进行逻辑与运算呢?不要小看这条指令,它的作用不容忽视,%esp指向堆栈顶部。这条指令其实是为了强制让%esp的值是16的倍数。为什么要16的倍数?这里必须懂得一个常识:Linux下的编译器GCC默认的堆栈是16字节对齐的,可能有些人要问为什么要对齐,对齐其实为了加快CPU的访问效率,这里你记住这点就可以了。

    movl    $0, %eax
    addl    $15, %eax
    addl    $15, %eax
    shrl    $4, %eax
    sall    $4, %eax

    看到这几句,又有更多人可能被吓到了,干嘛对%eax寄存器进行那么多的操作啊?的确,我也觉得没什么多大的必要,因为仔细看看这几条指令无非就是为了让%eax的值是0而已。看看刚开始 %eax = 0,经过两次addl之后,%eax的值变成30了,30其实就是0x11110,再下面两条指令是为了保证%eax最低5位的值全部为0。注意,这只是在我的机子上的反汇编指令,不同机器对此处理可能不一样,但有一点一样就是保证%eax的值是0。看看下面这条指令:

    subl    %eax, %esp

    看,%esp值减去%eax值后把结果送到%esp,所以经过这条指令后%esp值仍然是16的倍数,这就是保证%eax值是16的倍数的原因了。    

    call    function    
    movl    $0, %eax

    这个简单了,调用函数function,最后又把%eax寄存器的值清0,结束整个main函数了。这就是最简单的函数调用分析了,没有涉及到参数的传递,所以非常简单,下面就要开始讲到参数的传递了,事实上有了这个例子的分析,下面的简单多了。

2.函数原型: int function(int i)

    现在有了参数了,也有了返回值了,相对来说更比较复杂了。这里就得引入%esp寄存器值的变化了,不然就难以把问题分析清楚了,如果想形象一点地描述那就画图,自己画个图根据我的数据变化一起分析吧。看看一段简单的C代码:

    // C Code

    int function(int i)
    {
        return 2 * i;
    }

    int main(void)
    {
        int j = function(10);
        return 0;
    }

    之所以些这么简单只是为了我们分析问题的方便,懂得个原理就算是复杂的其实稍微再分析一下也就懂了。我们从main开始分析吧:

    main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        andl    $-16, %esp
        movl    $0, %eax
        addl    $15, %eax
        addl    $15, %eax
        shrl    $4, %eax
        sall    $4, %eax
        subl    %eax, %esp        #到这里其实和前面的例子基本一样,就不分析了
        movl    $10, (%esp)
        call    function
        movl    %eax, -4(%ebp)
        movl    $0, %eax
         leave
        ret

    看看上面的汇编代码,和前面一样的不分析。但是其中有句不一样:subl    $24, %esp ; 因为主函数里有两个临时变量i, j;这里为了有足够的空间留给临时变量所以干脆在堆栈里腾出24个字节空间。在看看下面的代码:

    movl    $10, (%esp) #====> %esp = 800, (800) = 10
,其中800是我们假设的地址值,(800)表示地址800的内容这里的(%esp)指的是%esp地址里的内容,
刚才我们假设这时候%esp的值是800, 那么地址为800的内容就是10了。执行函数调用了,注意在调用函数前其实是先把函数调用指令call之后的地址压栈,也就是call之后那条指令的IP值压栈,所以这时候 %esp =
796
;这里要弄明白为什么要把下条指令地址压栈,假设如果不把IP值压栈,那么当函数调用完毕后怎么能找到函数调用时的地址呢?也就是说如果没把IP压栈,那么函数调用完之后就回不到原来的执行地址了,就会造成程序执行顺序的错误!
下面列出函数function的汇编代码:

    function:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        addl    %eax, %eax
        popl    %ebp
        ret
    
    pushl %ebp; 经过这条指令后 %esp值减4,所以这时候%esp值是792。下面这句:

    movl    %esp, %ebp  #==============> %ebp = 792, %esp = 792, (792) = %ebp ;其中(792)表示地址792的内容

     movl    8(%ebp), %eax  #========> %eax = 10

    上面这句很多人可能不明白了,8(%ebp)指的是什么?8(%ebp)等于 : (%ebp + 8) ,这里注意,%ebp + 8
是表示一个地址值,加上括号表示存储在该地址上的内容。所以8(%ebp)其实就是地址为800的值,看前面地址800的值刚好是10!所以这句其实是把10复制给%eax寄存器.

    addl    %eax, %eax    #======> %eax = 20

    相当于2 * %eax, %eax这时候等于20了,刚好是实现了C代码中的 (2 * i);

    popl    %ebp        #=========> 恢复%ebp寄存器的值, %esp这时候等于796

    ret                    #=========>  函数调用完毕返回,这句其实是把刚才压栈的IP值弹出栈,执行这条指令后 %esp = 800

                        # 800!想想我们在调用函数的时候%esp也是800啊!这就是实现了“清栈”了,就是把调用函数所在的栈清除了!

    好了,函数 function的汇编代码分析完了,现在回头继续看看main函数里的下一条指令了。接下来是这句:

    movl    %eax, -4(%ebp)

    %eax寄存器存放的是什么?看function函数的代码,可以知道其实就是(2 *i)的值,所以返回值其实是通过%eax来传递的!传递到-4(%ebp)里去了,-4(%ebp) = (%ebp - 4);-4(%ebp)到底是什么呢?看看C代码,返回值传给变量j,那么-4(%ebp)会不会就是j呢?答案是肯定的!我们先看看%ebp的值是什么。看看
main函数的汇编代码,可以得出,%ebp其实指向了main函数的栈底部,但记不记得前面说的subl    $24,
%esp是为临时变量而留出的空间?没错,-4(%ebp) 就是存储在临时变量区域!也就是变量 j 了。
3. 函数原型:int function(int i, int j);
    现在参数是两个,不是一个了,两个到底该怎么处理呢?同样看C程序和相应的汇编代码:
    // C code


    int function(int i, int j)

    {

        return (i + j);

    }

    

    int main(void)

    {

        function(1, 2);

    }


    下面列出主要的汇编代码,没有全部列出来,因为一些和前面一样的代码已经分析过,不再罗嗦了。


main:

              # ... ...

        movl    $2, 4(%esp)

        movl    $1, (%esp)

        call    function


    看到没有?先把2送进栈里,再把1压栈,我们看看函数调用的C代码:function(1, 2); 2在右边,而1在左边,所以,当存在多参数的时候参数压栈其实是按从右向左的顺序压栈的。当参数都压栈后,就调用函数了。


    function:

        pushl   %ebp

        movl    %esp, %ebp

        movl    12(%ebp), %eax

        addl    8(%ebp), %eax

        popl    %ebp

        ret

    

    看函数的汇编代码:movl    12(%ebp), %eax ; 知道12是哪里来的吗?自己画图看看,结合前面的分析!一调用函数先压IP进栈,再压%ebp进栈,那么%esp值就被减去8了,再把%esp值复制到%ebp,就是这样而已!

4.函数原型:char *function(char *s);

    作为字符串函数其实道理也差不多,而且我觉得反而更简单,怎么个简单法,看代码吧:

    // C code

    char *function(char *s)
    {
        return s;
    }
    int main(void)
    {

        char *p = function("abcd");
    }
    列出并简单分析一下汇编代码:
    main:

              # ... ...

        movl    $.LC0, (%esp)

        call    function


    这里你可能会问 $.LC0 是什么。看看下面的定义:
    .LC0:

        .string "abcd"
    .LC0只是一个标志,就是字符串"abcd";所以说白了其实非常简单。movl    $.LC0, (%esp) ;就是把字符串送到地址为%esp的空间里。 

    下面看调用的函数的汇编代码了:
    function:

              #... ...

        movl    8(%ebp), %eax

          #.. ...
    8(%ebp)其实就是字符串了,依然建议你们自己画图看看,只要图一画,自然变得十分清晰。

5.函数原型:struct text function(int n);
    现在函数返回值类型换成是结构体了,差别就来了,不过其实道理还是那个样,本质的东西还是一样的。到结构体这里我就简单说几句而已。
    //C code
    struct text {
        int a;
    };


    struct text function(int n)
    {    
        struct text s;
        s.a = n;
        return s;
    }
    int main(void)
    {
        struct text t = function(10);
        return 0;
    }
    这里举的例子都非常简单,我们的目的只是为了分析不同函数调用和参数传递方式是怎样进行的。看汇编代码:
    main:

              # ... ...

        leal    -20(%ebp), %eax

        movl    $10, 4(%esp)

        movl    %eax, (%esp)

        call    function    

    到这个例子就只分析最重要的指令了,其他的大家可以尝试分析看看。在main函数开头,我的机子上GCC居然留出了40个字节给临时结构体变量!
开头有这句:subl    $40, %esp ; 有点惊讶,我也不知道为什么留这么大的空间。但这些数据我们先不管了,毕竟不是最重要的。
    leal    -20(%ebp), %eax
    看看上面这句,这条指令非常重要。把(%ebp - 20)的地址复制给%eax,%eax 的值其实是结构体中成员的地址。自己分析后面的代码证实一下。
    下面是function函数的汇编代码:
    function:

        pushl   %ebp

        movl    %esp, %ebp

        subl    $16, %esp

        movl    8(%ebp), %eax

        movl    12(%ebp), %edx

        movl    %edx, -4(%ebp)

        movl    -4(%ebp), %edx

        movl    %edx, (%eax)


    subl    $16, %esp        #为函数中临时结构体变量保留空间.
    在上面和条指令后面那几条指令都是按变量所在的地址赋给结构体变量成员了,有兴趣的朋友自己分析下,让我偷个懒吧。
5. 其他说明
    其实还有很多的类型都没讲,其中就有返回值为浮点数或者参数是浮点数,浮点数不讲了,因为这就比较复杂了,为了支持浮点数以及其他功能(比如说数学运算功能)就需要附加的指令和寄存器了,这些东西全部结合就称作“浮点单元”(Floating-point unit ---> FPU)。看看浮点数以及运算有自己的指令和寄存器,这些东西说起来就得一篇文章了。
    递归就不也用讲了,道理和前面一样,只是重复调用自己而已,就按照栈的顺序衍生过去而已然后最后一层层清栈了。另外还有可变参数函数,其实有了上面的基础来分析可变参数函数挺简单的,入栈顺序还是那样从右向左,只是你需要了解一下可变参数函数的内部到底怎么实现的,这里存在很强的技巧性,有兴趣的朋友可以去看看在头文件里面关于可变参数的几个宏,技巧性超强!
四、后记  
写到这里我的头都有点涨了,花了好几个钟头写的。本来后面还想再把结构体啊可变参数函数啊之类的说一下,但没什么激情了。抱歉一下。也不想重审文章了。

Figma 设计稿转前端代码:Cursor 实现 UI 还原的 4 种精准映射方法 揭示 Figma 设计稿转前端代码失效的根本原因在于四层语义未对齐(像素→布局、视觉→样式、交互→行为、意图→架构),提出以语义标注为核心的结构化喂数据方法,详解通过图层命名、Description 字段和 Markdown 上下文锚点实现高精度 React 组件生成,并给出可落地的避坑指南与实测提 阅读详情

相关推荐

2025-E题国一-简易自行瞄准装置:基于嘉立创地猛星/天猛星与K230的视觉巡线云台系统设计

本文详细介绍了2025年全国大学生电子设计竞赛E题一等奖作品——基于嘉立创地猛星/天猛星与K230的视觉巡线云台系统设计。该系统通过地猛星主控实现小车巡线,天猛星主控驱动云台,结合K230开发板的视觉识别能力,构建了一套完整的视觉巡线云台系统,实现了自动巡线与精准瞄准功能。文章深入剖析了硬件选型、双闭环PID控制算法、视觉识别与目标跟踪等核心技术,并分享了系统调试与避坑经验。

weixin_42596011的博客 675

关于C语言中函数调用和参数传递机制的探讨

函数,相信许多人也知道其重要性;一个文件往往由一个或者多个函数构成的。然而可能许多人还不知道函数调用的一些深层问题,所以我写的这篇文章一来是应了一个好朋友的要求而写,二来希望一些朋友能够从我这篇文章了解函数调用的机制。但是并不是每个人都可以完全读懂这文章,想完全读懂此文,我想必须具备三个条件: 一、对于C语言有一定的了解,最起码有一个整体的初步了解;   二、能够读懂UNIX/LINUX下的A

q286989429的专栏 611

麒麟和UOS系统编写桌面点击可执行文件

在桌面上创建一个快捷方式(.desktop 文件)来运行脚本。打开文本编辑器,创建一个新文件,如。这可以通过文本编辑器来完成。首先,确保您的脚本具有执行权限。现在,您应该可以双击桌面上的。保存此文件到桌面目录,例如,在桌面上创建一个快捷方式(文件来运行您的脚本了。使用图形界面或命令行给。现在您可以双击桌面上的。为您脚本的实际路径。是您脚本的实际路径。文件来运行您的脚本了。

u013934107的博客 7251

AAPCS关于ARM寄存器的定义

AAPCS对ARM结构的一些标准做了定义,在这里我们只重点介绍函数调用部分,如图8所示,AAPCS为ARM的R0~R15寄存器做了定义,明确了它们在函数中的职责: 图 8  AAPCS关于ARM寄存器的定义 函数调用时的规则如下: 1. 父函数与子函数间的入口参数依次通过R0~R3这4个寄存器传递。父函数在调用子函数前先将参数存入到R0~R3中,若只有一个参数则使用R0传递,2个

y249077293的专栏 2697

大疆精灵3无人机2.4GHz通信系统实测:跳频扩频技术深度解析

跳频扩频(FHSS)是一种经典的扩频通信技术,其核心原理是通过让载波频率在一个宽频带上按伪随机序列快速跳变,将窄带信号的能量扩散开来。这种技术能有效对抗固定频率干扰,提升通信的可靠性和抗截获性,在无线遥控、军事通信等领域具有重要价值。在无人机、物联网等对实时性与可靠性要求高的应用场景中,FHSS技术能显著增强复杂电磁环境下的链路鲁棒性。本文以频谱分析仪实测大疆精灵3无人机为例,深入剖析其2.4GHz频段遥控与图传系统的工作特性,揭示了其采用的成熟FHSS方案,包括约76MHz的总跳频带宽、39个信道及约2M

weixin_33935636的博客 311

解决mamba_ssm库安装难题:两种高效方法详解

本文详细解析了mamba_ssm库安装失败的常见原因,并提供了两种高效的解决方案。针对其依赖C++/CUDA扩展编译的特性,文章手把手指导用户通过GitHub克隆源码编译安装,或寻找匹配的预编译Wheel文件直接安装,帮助开发者彻底解决环境配置难题,顺利部署Mamba模型。

weixin_29056865的博客 554

结构体作为函数参数传递

结构变量是一个标量,它可以用于其他标量可以使用的任何场合,但把结构体作为参数传递给一个函数要注重效率 例如下面这样一个结构体: #define PRODUCT_SIZE 20 typedef struct{ char product[PRODUCT_SIZE]; int quantity; float unit_price; float total_amo...

Tanner_SL的博客 1万+

金蝶云星空API实战:从零到一,用Postman打通业务数据流

本文详细介绍了如何使用Postman调试金蝶云星空API,从创建第三方应用到实战调试技巧,帮助开发者快速打通业务数据流。文章重点讲解了API配置的关键细节、常见问题排查方法以及自动化测试方案,提升开发效率与准确性。

weixin_33712987的博客 519

C#类与结构体究竟谁快——各种函数调用模式速度评测

以前我一直有个疑惑——在C#中,究竟是类(class)比较快,还是结构体(struct)比较快?当时没有深究。 最近我遇到一个难题,需要将一些运算大的指针操作代码给封装一下。原先为了性能,这些代码是以硬编码的形式混杂在算法逻辑之中,不但影响了算法逻辑的可读性,其本身的指针操作代码枯燥、难懂、易写错,不易维护。所以我希望将其封装一下,简化代码编写、提高可维护性,但同时要尽可能地保证性能。由于那些指...

weixin_34049948的博客 312

STM32cubemx配置驱动DHT11模块

本篇文章将带大家学习使用DHT11湿温度模块,使用这个模块可以获取当前的温度和湿度数据。DHT11可以用于智能家居,蔬菜大棚等项目当中适用范围广。DHT11 是一种数字式温湿度传感器,由杜邦公司推出。它可以测量环境中的温度和湿度,并将测量结果以数字形式传输给微控制器或其他电子设备,适用于通过数字信号读取环境温湿度的应用场景。DHT11 传感器使用单总线数据交换协议,可以通过一个 I/O 引脚进行控制和数据传输。

m0_49476241的博客 5912

解决QT5调用软键盘qvirtualkeyboard时崩溃问题ime_pinyin::MatrixSearch::init_user_dictionary

但是偶尔也会出现问题,譬如,我这边在Arm系统上跑着程序的时候,点击用户编辑框,输入法弹出,但是再次点击密码输入框,连程序都给我崩溃了。只是提示错误:void ime_pinyin::MatrixSearch::init_user_dictionary。首先确保你的Arm版QT库已经编译了软键盘,位置有时候就和lib同级目录,qtvirtualkeyboard。QT5中的软键盘qvirtualkeyboard很好用,很多人开发嵌入式端应用的时候都会用到。此外还有很多其他的输入法字典,像。

有问题或资源连接有缺失的可以私信公众号新潮看世界获取 2070

【C语言】结构体与内存操作函数 总结

C 语言的内存管理,分成两部分。一部分是系统管理的系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。这些变量存放的区域称为”栈“(stack),”栈“所在的内存是系统自动管理的。另一部分是用户手动管理的用户手动管理的内存,主要是程序运行的整个过程中都存在的变量(全局变量),这些变量需要用户手动从内存释放。如果使用后忘记释放,它就一直占用内存,直到程序退出,这种情况称为**”内存泄漏“(memory leak)。

hdz_wiz_csdn的博客 2749

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

关于C语言中函数调用和参数传递机制的探讨(zz)

关于C语言中函数调用和参数传递机制的探讨    函数,相信许多人也知道其重要性;一个文件往往由一个或者多个函数构成的。然而可能许多人还不知道函数调用的一些深层问题,所以我写的这篇文章一来是应 了一个好朋友的要求而写,二来希望一些朋友能够从我这篇文章了解函数调用的机制。但

冒大烟的羊肉串的专栏 1259

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

C语言和汇编语言函数调用

C语言和汇编语言函数调用关系 1.汇编语言函数调用 X86结构中,cs寄存器和rip寄存器共同控制着CPU要执行的下一条指令(当前在不同的模式中控制方式不同,如:实地址2模式和保护模式,长模式等),一般会按照指令在内存中存储的顺序,依次执行。如果想要在普通程序(除去系统调用和中断)执行中跳转到某一条指令,就需要使用JMP、CALL、RET及其变种指令。 jmp指令是无条件跳转指令,直接跳转到某条指令。 call指令在执行时,会先将下一条指令地址压栈,然后跳转到目标指令。 ret指令执行时,会从当前栈顶弹出

KeePromise的博客 1769

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

c语言如何在弹出框中添加参数,涉及C语言中参数调用和参数传递机制的探讨_非常编程 专注编程...

原帖及研讨:涉及C语言中参数调用和参数传递机制的探讨参数,相信许多人也明白其很重要性;一个的文档往往由一个的或者多个参数构成的。然而估计许多人还不明白参数调用的一些深层难点,所以我写的这篇文本作品一来是应了一个的好哥们的要求而写,二来期望一些哥们能够从我这篇文本作品了解参数调用的机制。但是并不是每个人都没成绩完全读懂这文本作品,想完全读懂此文,我想必需具备三个要求:一、对于C语言有确定的了解,最起...

weixin_30603711的博客 249

关于C语言中函数调用和参数传递机…

[原创]关于C语言中函数调用和参数传递机制的探讨 关于C语言中函数调用和参数传递机制的探讨 函数,相信许多人也知道其重要性;一个文件往往由一个或者多个函数构成的。然而可能许多人还不知道函数调用的一些深层问题,所以我写的这篇文章一来是应了一个好朋友的要求而写,二来希望一些朋友能够从我这篇文章了解函数调用的机制。但是并不是每个人都可以完全读懂这文章,想完全读懂此文,我想必须具备三个条件: 一、对

shangyaowei的专栏 709

转载-关于C语言中函数调用和参数传递机制的探讨

[原创]关于C语言中函数调用和参数传递机制的探讨 关于C语言中函数调用和参数传递机制的探讨函数,相信许多人也知道其重要性;一个文件往往由一个或者多个函数构成的。然而可能许多人还不知道函数调用的一些深层问题,所以我写的这篇文章一来是应了一个好朋友的要求而写,二来希望一些朋友能够从我这篇文章了解函数调用的机制。但是并不是每个人都可以完全读懂这文章,想完全读懂此文,我想必须具备三个条件:一、对于C语言...

weixin_34216107的博客 90

C语言函数调用过程-汇编分析

一个人仰起的面孔充满了画面,在微弱的光线里无法看清这张面孔的细部,只能看出他的眉骨和颧骨很高,嘴唇长而薄。镜头继续拉近这似乎已不可能再近的距离,一双深陷的眼睛充满了画面,黑暗中的瞳仁中有一些银色的光斑,那是映在其中的变形的星空。 图像定格,一声尖利的鸣叫响起,排险者告诉人们,预警系统报警了。 “为什么?”总工程师不解地问。 “这个原始人仰望星空的时间超过了预警阀值,已对宇宙表现出了充分的好奇,到此为止,已在不同的地点观察到了十例这样的超限事件,符合报警条件。” “如果我没记错的话,你前面说过,只有.

u010643777的专栏 1084

Linux gcc 利用反汇编来研究C语言函数堆栈的分配方式

越来越感觉学习C和汇编才是最能接近计算机本质的途径。所以,今天开始研究汇编了,先从gcc反汇编开始。     首先是下面的C代码: #include int sum(int a,int b) {     char s=1;     return a+b; } int main(void) {     int a,b;     a = 1;     b = 2;

计算机的自我意识 6722

c语言函数和汇编指令意思一样,一段C语言和汇编的对应分析,揭示函数调用的本质...

原标题:一段C语言和汇编的对应分析,揭示函数调用的本质最近网易云课堂开放了一节叫Linux内核分析的课程。一直对操作系统和计算机本质很感兴趣,于是进去看了下,才第一堂课,老师就要求学生写一篇关于课时1的博客作为作业。对于这种新颖的作业形式,笔者相当惊讶。好吧,作为任务,还是完成一下吧,刚好需要消化一下。本文将会按照要求,将一段C语言代码编译成汇编,并给予分析和自己的思考。首先对会涉及到的一些CPU...

weixin_42393127的博客 434

一段C语言和汇编的对应分析,揭示函数调用的本质

最近网易云课堂开放了一节叫Linux内核分析的课程。一直对操作系统和计算机本质很感兴趣,于是进去看了下,才第一堂课,老师就要求学生写一篇关于课时1的博客作为作业。对于这种新颖的作业形式,笔者相当惊讶。好吧,作为任务,还是完成一下吧,刚好需要消化一下。本文将会按照要求,将一段C语言代码编译成汇编,并给予分析和自己的思考。 本文作者周平,原创作品转载请注明出处 首先对会涉及到的一些CPU寄

liqiang981的博客 886

【C语言】函数栈帧的创建和销毁

这里也一样,把esp的地址赋给ebp,也就意味着,ebp指向的位置改变了,但我们之前说这个ebp不是栈底指针吗?这里就很明显的能看出特点,我们看到main函数是在Add函数的下面,也就意味着Add函数是后调用的,那么main函数下面还有一个函数,叫main。执行到这条指令时,程序流会跳转到函数的起始地址,并将当前指令的地址保存在堆栈上,以便在函数执行完毕后返回到正确的位置。这里还有一个重要到底知识点,esp和ebp是用来维护函数栈帧的,也就是说,现在在调用哪个函数我esp,ebp就维护哪个函数的函数栈帧。

HeXIAOxia_的博客 686

go 函数调用规则(一)

gogogo

yongbaoii的博客 1239

栈的应用-函数调用

栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 当发生函数调用的时候,栈空间中存放的数据是这...

leemboy的博客 1426
上一篇: 函数调用方式——_stdcall与_cdecl的区别(函数调用方式)
下一篇: C++中函数调用时的三种参数传递方式
噗噗噗~
博客等级 码龄12年 2粉丝 3原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值