跳过正文
  1. Posts/

C语言变长参数

·2 分钟

说起C语言的变长参数,可能听起来比较陌生,因为很少会需要自己实现。不过想一下scanf和printf,参数个数的确是不固定的。

stdarg.h 中提供以一套机制来实现变长参数。以及,要说明的是,变长参数不是什么黑魔法,原理依赖于stack frame的结构,具体可以参考x86-calling-conventions   简单来说,由于函数参数入栈的顺序是固定的,**因此一旦我们知道某函数帧的栈上的一个固定参数的位置,我们完全有可能推导出其他变长参数的位置 **

在实现上,需要了解的是:

  * va_list,一个类型,可以看做是变长参数列表;
  * [va_start](http://en.cppreference.com/w/cpp/utility/variadic/va_start),用来初始化变长参数列表的宏,声明为void va_start( va_list ap, parm_n );  ap为va_list变量,parm_n为变长参数前一个变量(C语言要求至少有一个named variable作为函数的parameter)
  * [va_arg](http://en.cppreference.com/w/cpp/utility/variadic/va_arg),用来得到下一个参数的宏,声明为T va_arg( va_list ap, T ); **返回的类型取决于传入的类型T。特别注意:"If `va_arg` is called when there are no more arguments in `ap`, the behavior is undefined."**
  * [va_end](http://en.cppreference.com/w/cpp/utility/variadic/va_end) ,用来将va_list释放的宏。

下面看一个例子就明白怎么用了orz

 1    #include <stdio.h>
 2    #include <stdarg.h>
 3    
 4    /* print all args one at a time until a negative argument is seen;
 5       all args are assumed to be of int type */
 6    void printargs(int arg1, ...)
 7    {
 8      va_list ap;
 9      int i;
10    
11      va_start(ap, arg1); 
12      for (i = arg1; i >= 0; i = va_arg(ap, int))
13        printf("%d ", i);
14      va_end(ap);
15      putchar('\n');
16    }
17    
18    int main(void)
19    {
20       printargs(5, 2, 14, 84, 97, 15, -1, 48, -1);
21       printargs(84, 51, -1);
22       printargs(-1);
23       printargs(1, -1);
24       return 0;
25    }
26    
27    
28    output:
29    5 2 14 84 97 15
30    84 51
31    
32    1

如果想研究c语言中变长参数的具体实现,可以参考 也谈C语言变长参数

参考资料:

Variable numbers of arguments

相关文章

x86 calling conventions

·3 分钟
x86的调用约定主要说的是这几件事: The order in which atomic (scalar) parameters, or individual parts of a complex parameter, are allocated How parameters are passed (pushed on the stack, placed in registers, or a mix of both) Which registers the called function must preserve for the caller (also known as: callee-saved registers or non-volatile registers) How the task of preparing the stack for, and restoring after, a function call is divided between the caller and the callee 调用约定实际上并不唯一

【施工完成】MIT 6.828 lab 1: C, Assembly, Tools and Bootstrapping

·40 分钟
花费了30+小时,终于搞定了orz # Part 1: PC Bootstrap # The PC’s Physical Address Space # 8086/8088时代 # 1+------------------+ <- 0x00100000 (1MB) 2| BIOS ROM | 3+------------------+ <- 0x000F0000 (960KB) 4| 16-bit devices, | 5| expansion ROMs | 6+------------------+ <- 0x000C0000 (768KB) 7| VGA Display | 8+------------------+ <- 0x000A0000 (640KB) 9| | 10| Low Memory | 11| | 12+------------------+ <- 0x00000000 由于8086/8088只有20跟地址线,因此物理内存空间就是2^20=1MB.地址空间从0x00000到0xFFFFF.其中从0x00000开始的640k空间被称为"low memory",是PC真正能使用的RAM。从 0xA0000 到 0xFFFFF 的384k的non-volatile memory被硬件保留,用作video display buffers和BIOS等。

边界的链码,归一化链码,差分玛,形状数

·4 分钟
复习一下数字图像处理。 # # # # 按照我自己的理解简单来说: # 原链码:按照任意起点走边界一周,方向按照上图对应的表示,得到的数字序列就是原链码。 # 归一化链码:为了解决原链码中起点不唯一而产生的序列不唯一的问题,规定,对于所有起点得到的原链码中,字典序最小的即为归一化链码(由于序列都是自然数,因此字典序最小也可以理解成,把该序列看成有前导0的自然数之后的数值之后的数值最小。 # 差分码:为了解决图形旋转之后,原链码和归一化链码都会发生变化,引入差分码。n 位原链码(或归一化链码,由于归一化链码只是一种特殊的原链码,之后不再单独强调)可以得到 n-1 位差分码。具体来说,对于原链码 a[i](i 属于 1..n),可以得到差分码 b[i],b[i] = ((a[i+1] - a[i]) + mod) % mod(i 属于 1..n-1),mod 根据实际有几个方向决定,通常为 4 或者 8。 # 形状数:需要强调的是,形状数也是一个序列,而不是一个数。其实形状数就是把差分码按照字典序排序之后,最小的序列。形状数的阶数是该序列的长度。 # Freeman链码(弗雷曼链码)是指用曲线起始点的坐标和边界点方向代码来描述曲线或边界的方法,常被用来在图像处理、计算机图形学、模式识别等领域中表示曲线和区域边界。它是一种边界的编码表示法,用边界方向作为编码依据,为简化边界的描述,一般描述的是边界点集。