windows程序设计第二章学习笔记

从第二章开始开始有难度了,单靠看书是不行的,许多知识点需要自己查阅相关资料方能理解。个人认为该章难度主要集中在几个API函数运用上

在windows编程中不能使用pirntf,但能使用sprintf,vsprintf系列

int sprintf( char *buffer, const char *format [,argument] ... );

int vsprintf( char *buffer, const char *format, va_list argptr );

两者区别在于如果需要写一个自定义的可变参数的函数,则应该用vsprintf,因为该函数有个指向格式化参数数组的指针(注意在写可变参数函数时一般定义为CDCEL,特别注意的是不能用__stdcall,因为__stdcall要由被调用者函数自动还原堆栈,而CDCEL是由调用者函数负责还原堆栈,详细可查阅函数命名调用约定),但在自几写的可变参数函数里如何得到格式化参数的指针呢,从书上p33发现作者运用了va_list,va_start,va_end宏,但看了书上的例子之后总觉得还是云里雾里,于是查看了<stdarg.h>关于宏的定义,"Yes,I got it",我不禁欢呼,原来在该头文件中主要定义了5个宏,下面分别阐述一下:

va_list宏:
书上p33的用法:va_list pArgs;
<stdarg.h>中的定义:
                #ifndef _VA_LIST_DEFINED
                #ifdef  _M_ALPHA
                    typedef struct {
                        char *a0;       /* pointer to first homed integer argument */
                        int offset;     /* byte offset of next parameter */
                    } va_list;
                #else
                    typedef char *  va_list;
                #endif
                #define _VA_LIST_DEFINED
                #endif

pArgs为char*类型,是指向格式化参数的指针类型,pArgs作为结构体类型什么时候用到还不清楚。

va_start(ap,v)宏:
书上p33的用法:va_start(pArgs,szFormat)
<stdarg.h>中的定义:
            #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
定义_INTSIZEOF(v)宏主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,下图是函数的参数在堆栈中的分布位置,看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是最后一个固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址。
高地址|-----------------------------|
|函数返回地址 |
|-----------------------------|
|....... |
|-----------------------------|
|第n个参数(第一个可变参数) |
|-----------------------------|<--va_start后ap指向
|第n-1个参数(最后一个固定参数)|
低地址|-----------------------------|<-- &v

va_end(ap)宏:
书上p33的用法:va_end(pArgs)
<stdarg.h>中的定义:
            #define va_end(ap)      ( ap = (va_list)0 )
该宏用来将指向格式化参数指针赋为NULL,来杜绝野指针。

va_arg(ap,t)宏:
用法:
        void simple_va_fun(int i, ...)
  {
        va_list arg_ptr;
        int j=0;
              va_start(arg_ptr, i);
        j=va_arg(arg_ptr, int);
        va_end(arg_ptr);
        printf("%d %d/n", i, j);
        return;
  }
       void main()
       {
             simple_va_fun(100,200);
       }
<stdarg.h>中的定义:
      #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
首先ap+=_INTSIZEOF(t),已经指向下一个参数的地址了.然后返回ap-_INTSIZEOF(t)的int*指针,这正是第一个可变参数在堆栈里的地址(如下图所示).然后用*取得这个地址的内容(参数值)赋给j。

高地址|-----------------------------|
|函数返回地址 |
|-----------------------------|
|....... |
|-----------------------------|<--va_arg后ap指向
|第n个参数(第一个可变参数) |
|-----------------------------|<--va_start后ap指向
|第n-1个参数(最后一个固定参数)|
低地址|-----------------------------|<-- &v

_INTSIZEOF(n)宏:
<stdarg.h>中的定义:
         #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
_INTSIZEOF(n)宏是为了考虑那些内存地址需要对齐的系统,在va_start(ap,v)宏和va_arg(ap,t)宏定义中用到,从宏的名字来应该是跟sizeof(int)对齐。一般的sizeof(int)=4,也就是参数在内存中的地址都为4的倍数。比如,如果sizeof(n)在1-4之间,那么_INTSIZEOF(n)=4;如果sizeof(n)在5-8之间,那么_INTSIZEOF(n)=8。

vsprintf和sprintf使用时有两个要注意的地方:
1.与printf一样当格式字符串与被格式化的变量不匹配时,可能会运行错误并可能造成程序崩溃。
2.用户的自定义的字符缓冲区必须足够大来存放结果,否则会造成缓冲区溢出(非法内存访问)。
所以由此引申出它们的第一个变种:最大长度版

int _snprintf( char *buffer, size_t count, const char *format [, argument] ... );

int _vsnprintf( char *buffer, size_t count, const char *format, va_list argptr );

它们比原来多了一个参数,该参数用来表明用户定义的缓冲区能够容纳的字符数,如果缓冲区已经到达count个字符,则剩余字符不予复制,如果剩余字符(不包括'/0')大于0,则该函数返回-1,否则返回总的copy字符数(不包括'/0'),所以说最大长度版相对比较安全,但效率相对较慢(在缓冲区能够容纳格式化字符数时至少是这样),以下是我做的一个试验程序:
1。#include <stdio.h>

void main()
{
 char szBuffer1[2];
 int iReturn1;
 iReturn1=sprintf(szBuffer1,"abc");    //非法访问了两块内存,并赋值为c和'/0'
 printf("szBuffer1=%s,iReturn1=%d/n",szBuffer1,iReturn1); 
}

2。#include <stdio.h>

void main()
{
 char szBuffer1[2];
 int iReturn1;
 iReturn1=_snprintf(szBuffer1,2,"abc");      //只赋值了a和b
 printf("szBuffer1=%s,iReturn1=%d/n",szBuffer1,iReturn1);
}
但不是说这样就安全了,前提是我们一定要参数count传递正确:

1.如果count>缓冲区实际能容纳的字符数,则仍就可能造成缓冲区溢出。
2.如果count<缓冲区实际能容纳的字符数,则可能会造成没有完全利用内存空间。

前面的四个函数都要包括stdio.h头文件,属于c运行时函数,如果我只想用一个windows.h头文件怎么办,于是有了sprintf,vsprintf第二个变种:windows版

int wsprintfA( char *buffer, const char *format [,argument] ... );

int wvsprintfA( char *buffer, const char *format, va_list argptr );

他们功能上基本与sprintf和vsprintf等价,但不能处理浮点格式(搞不懂微软在干什么?-:))

所以上述三类函数各有优缺点,他们分别又有ascii和unicode以及通用(视编译设置而定)三种,可参看p33表2-1。值得注意的是在通用函数中如果要按unicode编译则要考虑两种情况,如果该函数是c语言运行时函数,则需要指定_UNICODE,而windows函数需要指定UNICODE。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值