• 多参的实现原理


    相信大家都使用过C语言的库函数:printf("%d%d", 1, 2)的吧,使用确实很方便功能也很强大。

    但是为什么它可以接受多个参数呢?

    现在我们来解析一下多参的实现原理,网上也找了一些文章。发现解析得都不全面。并且有BUG。

    先看如下源码:

    #include <windows.h>
    #include <stdio.h>
    #include <winnt.h>
    
    void MySprintf(char* szBuffer, const char* szFormat, ...)
    {
      va_list pa;    // 定义一个指针
      va_start(pa, szFormat);  // 把指针赋值为第一个参数的值
      vsprintf(szBuffer, szFormat, pa);// 
      va_end(pa); // 清空
    }
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        PSTR szCmdLine, int iCmdShow)
    {
      char szBuffer[128] = {0};
      MySprintf(szBuffer, "%d%d%d", 1,2,3);
      return 0 ;
    }

    首先,我们函数压参顺序是重右往左,对应的栈空间内存地址从高到低。

    MySprintf(szBuffer, "%d%d%d", 1,2,3);

    这行代码,分别想栈中压入3, 2, 1, 然后再是szFormat的内存空间,在是szBuffer的内存空间.

    内存结构如下:

    熟悉函数调用的几步过程,压入参数,保存返回地址和栈顶,开辟局部变量空间.

    现在保存了返回地址,然后继续执行的话,就是保存栈顶和开辟va_list pa所需的内存空间.

    va_list其实也就是char* 类型。占4个字节。继续执行后如下:

    看到了吗,va_start(pa, szFormat); 这条语句计算出了参数的起始地址,

    它是如何计算出的呢?我们既然知道了内存布局, 那szFormat取地址+sizeof(va_list)。

    说简单点也就是:szFormat的内存地址 + 4个字节. 不就刚好偏移到第一个参数的地址处了吗?

    并且, 以上的MySprintf函数等同于下面这种写法:

    void MySprintf(char* szBuffer, const char* szFormat, ...)
    {
      char* pCh = NULL;
      pCh = (char*)&szFormat + sizeof(pCh);
      vsprintf(szBuffer, szFormat, pCh);
      pCh = NULL;
    }

    现在,我们得到了参数的内存首地址,但是还缺少信息。缺什么信息?

    当前地址处有几个参数,每个参数什么类型(占用字节数)?

    关键的地方就在这里了。szFormat中有类型信息信息,并且有类型信息的个数,我们可以通过遍历字符串,

    找出类型信息的顺序和个数。然后根据遍历找到的信息。来解析参数的内存首地址。

    具体的做法.在vsprintf中有实现,下面是拷贝vsprintf的实现代码,

    #ifndef _COUNT_
    
    int __cdecl vsprintf (
            char *string,
            const char *format,
            va_list ap
            )
    #else  /* _COUNT_ */
    
    int __cdecl _vsnprintf (
            char *string,
            size_t count,
            const char *format,
            va_list ap
            )
    #endif  /* _COUNT_ */
    
    {
            FILE str;
            REG1 FILE *outfile = &str;
            REG2 int retval;
    
            _ASSERTE(string != NULL);
            _ASSERTE(format != NULL);
    
            outfile->_flag = _IOWRT|_IOSTRG;
            outfile->_ptr = outfile->_base = string;
    #ifndef _COUNT_
            outfile->_cnt = MAXSTR;
    #else  /* _COUNT_ */
            outfile->_cnt = count;
    #endif  /* _COUNT_ */
            /*
            简单说明:
                关键代码处,大家直接跟进去即可,
                先是做些判断,然后设置一些标志位,
                最后把数字根据设置的标志转为字符串.
            */
            retval = _output(outfile,format,ap );
            _putc_lk('\0',outfile);
    
            return(retval);
    }

    本人菜鸟,水平有限,望各路大牛指点!

  • 相关阅读:
    JavaScript----特效代码
    坑!vue.js在ios9中失效
    MySQL的ibdata1文件占用过大
    Ubuntu搭建Gitlab服务器
    Logstash+Kibana部署配置
    Kafka+Zookeeper集群搭建
    ES5.0集群搭建
    Kibana使用高德地图
    Zabbix安装客户端agent(windows和Centos7)
    Centos/Rhel7部署Zabbix监控(部署篇之服务器篇)
  • 原文地址:https://www.cnblogs.com/ziolo/p/3036346.html
Copyright © 2020-2023  润新知