• 函数的用法


    函数是c语言模块化编程方式的体现。

    变参函数的实现方法

    变参函数的定义一般长这样:

    printf(const char*, ...);
    scanf(const char*, ...);
    
    • 1
    • 2

    其中,...是占位符,其作用是告诉编译器对所有参数做压栈处理。
    数组的压栈方式是从右向左,而函数参数的压栈方式也是从右向左,因为这种方式易于处理变参函数。
    举个例子:

    #include
    void print(int n,...)
    {
            int *p,i;
            p=&n+1; //跳过第一个参数,递增到第二个参数
            for(i=0;i<n;i++)
            {
                    printf("%d\t",p[i]); //通过访问符递增地址,因为不会报错,所以可以访问,,,,,
            }
    		printf("\n");
            return ;
    }
    
    int main()
    {
            print(4,12,34,56,78);
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    12	34	56	78
    
    • 1

    注意:使用变参函数时,如果没有给出变参函数的个数,直接给出第一个参数,那么必须约定一个参数作为结束标志。

    我用的mac,输出的结果不对,而且最后一位是1,似乎是从左到右压栈的。

    在C语言中,系统提供了实现变参函数的宏,用法如下:

    #include
    #include
    void print(int n,...)
    {
    	int arg,i;
    	va_list p;
    	va_start(p,n);
    	for(i=0;i<n;i++)
    	{
    		arg=va_arg(p,int);
    		printf("%d\t",arg);
    	}
    	printf("\n");
    	va_end(p);
    	return ;
    }
    int main()
    {
    	print(3,12,34,56);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    12	34	56
    
    • 1

    系统对于这些宏的实现效果如下:

    //定义一个char类型指针
    typedef char *   va_list;//处理字节对齐
    ​​​​​​​​#define _INTSIZEOF(n)    ( (sizeof(n) + sizeof(int) - 1) &(sizeof(int) - 1) )//处理指针类型
    ​​​​​​​​#define va_start(ap,v)   ( ap = (va_list)&v + _INTSIZEOF(v) )//处理参数
    ​​​​​​​​#define va_arg(ap,t)     ((t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )//清除指针
    ​​​​​​​​#define va_end(ap)        ( ap = (va_list)0 )​​
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    函数指针

    函数指针是指向函数的指针。
    和数组名是数组的首地址一样,函数名称是函数的首地址。
    因此函数指针可以指向不同的函数,以调用不同的函数。

    为了方便,通常函数指针会以宏定义的方式进行定义:

    typedef void (*funp)(int a,int b);
    void fun(int a, int b);
    funp=fun;
    funp(3,4);
    
    • 1
    • 2
    • 3
    • 4

    这样函数指针funp就指向了函数fun,并通过funp传参进而调用fun。
    函数指针的作用基本上就是作为接口,解耦两个模块。

    函数之间的调用关系

    函数压栈的方式是:
    参数从右到左,局部变量从左到右。

    函数调用时,会发生如下动作:
    将栈底指针ebp入栈,此时调用函数的ebp保存起来了;
    将ebp寄存器赋值esp,此时ebp寄存器指向原来栈帧的栈顶;
    将esp指针减16,此时建立了新栈帧,栈底是原来栈帧的栈顶,栈顶是减16后保存了ebp;
    还有一个是指令指针eip,指的是栈帧中执行到哪条指令了。

    #include
    void test(int a,int b,int c)
    {
    	...
    }
    int main()
    {
    	test(1,2,3);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当调用test函数后,压栈状态是这样的:
    早期的压栈方式
    从内存高地址存,首先存参数,从右向左,然后存指令寄存器eip的值,他的内容是下一条指令的地址,然后存栈底寄存器ebp的值,他的内容是调用者栈帧的栈底,然后再存数组内容,从右向左。

    早期的压栈方式如上:先压栈参数,再压栈寄存器,再压栈临时变量;
    新版本的压栈方式是:先压栈参数,再压栈临时变量,再压栈寄存器。
    这样我们就可以通过这种方式获取:

    void test()
    {
    	int a;
    	printf("%p",*(&a+1));
    	return ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    .NET App 与Windows系统媒体控制(SMTC)交互
    单例 模式
    2023年【T电梯修理】考试题及T电梯修理考试报名
    Swift -- 数组高阶函数
    如何用优盘加密自己的电脑:人离后自动锁定
    Cookie与Session简单入门
    webgl 系列 —— 绘制一个点(版本2、版本3、版本4、版本5)
    Minecraft 1.20.x Forge模组开发 06.建筑生成
    php 十大排序算法
    计算物理专题----蒙特卡洛积分实战
  • 原文地址:https://blog.csdn.net/weixin_39759247/article/details/126142918