[C language] Variable parameter list


foreword

The variable parameter list is used like an array. If you have studied the function stack frame, you can find that it is actually accessed continuously in a space defined by the stack area, but it does not support direct access in the middle part.


Disclaimer: All tests below were done under x86, vs2013.

1. What is a variable parameter list?


insert image description here
In the first lesson of our initial C language, we have been exposed to variable parameter lists. In the process of printf, we can usually pass a large number of parameters to be printed, but we don't know how he does it. Today Let us analyze it.



Second, how to use variable parameter list


First we have to introduce the header file of windows.h
and then we have to introduce the following macros. Here we briefly describe its functions, and there will be a detailed explanation later, here is for the convenience of everyone to get started.

typedef char* va_list;  //类型的重定义

#define _ADDRESSOF(v) (&(v))//一个取地址的宏。

1._ADDRESSOF : Take the address of the incoming variable.

#define _INTSIZEOF(n) \
 ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))




2. _INTSIZEOF : This macro function is to round the type of n to a multiple of 4.

#define _INTSIZEOF(n)\
  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

The following piece of code explains:

#pragma pack(1)//设置默认对其数为1
struct A
{
    
    
	char ch[11];
};

int main()
{
    
    
	printf("int : %d\n", _INTSIZEOF(int));
	printf("double: %d\n", _INTSIZEOF(double));
	printf("short: %d\n", _INTSIZEOF(short));
	printf("float: %d\n", _INTSIZEOF(float));
	printf("long long int: %d\n", _INTSIZEOF(long long int));
	printf("struct A:%d\n", _INTSIZEOF(struct A));
	return 0;
}

result:
insert image description here


3. __crt_va_start_a : Take the address of the variable v and cast it to char* and then point to the v type to log the number, that is, find the variable in the first variable parameter list!

#define __crt_va_start_a(ap, v) \
((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))



4. __crt_va_arg : Point ap to the next location to be accessed in advance, and return the content currently accessed. Note that ap after += points to the next address to be accessed, but the content returned is the current one.

#define __crt_va_arg(ap, t)   \
      (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))



5. __crt_va_end : Set ap to NULL.

#define __crt_va_end(ap)\
 ((void)(ap = (va_list)0))

Next we look at the following definitions.
#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end

Test: Find the largest set of data that is not stored in the array and return it.

int Find_Max(int num, ...)
{
    
    
	//定义一个char* 的变量arg
	va_list arg;
	//将arg指向第一个可变参数
	va_start(arg, num);
	//将max置成第一个可变参数,然后arg指向下一个可变参数
	int max = va_arg(arg, int);
	//循环num-1次,访问完剩下的可变参,找到最大的赋值给max
	for (int i = 1; i < num; ++i)
	{
    
    
		int r;
		if (max < (r = va_arg(arg, int)))
		{
    
    
			max = r;
		}
	}
	//将arg指针变量置成NULL,避免野指针
	va_end(arg);
	return max;
}
int main()
{
    
    
	int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
	printf("ret :%d\n", ret);
	return 0;
}

result:
insert image description here


3. In-depth analysis of macros


Although the process address space under Linux is arranged from high to low, because the memory under VS is from low byte to high byte, our stack will be different from that drawn under Linux, but they are all directed towards Extends in the direction of low addresses. This is for everyone to understand.

Schematic diagram of Linux process address space: schematic diagram of
insert image description here
code stack frame:
insert image description here


implicit type conversion


For example, when we execute the following code, when we pass parameters in char type, but the function body is still obtained in int steps, will there be an error at this time?

#include<stdio.h>
#include<windows.h>
int Find_Max(int num, ...)
{
    
    
	//定义一个char* 的变量arg
	va_list arg;
	//将arg指向第一个可变参数
	va_start(arg, num);
	//将max置成第一个可变参数,然后arg指向下一个可变参数
	int max = va_arg(arg, int);
	//循环num-1次,访问完剩下的可变参,找到最大的赋值给max
	for (int i = 1; i < num; ++i)
	{
    
    
		int r;
		if (max < (r = va_arg(arg, int)))
		{
    
    
			max = r;
		}
	}
	//将arg指针变量置成NULL,避免野指针
	va_end(arg);
	return max;
}

int main()
{
    
    
	char a = '1'; //ascii值: 49
	char b = '2'; //ascii值: 50
	char c = '3'; //ascii值: 51
	char d = '4'; //ascii值: 52
	char e = '5'; //ascii值: 53
	int ret = Find_Max(5, a, b, c, d, e);
	//int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
	printf("ret :%d\n", ret);
	system("pause");
}

Answer:
No, because the parameters are passed through registers when pushing the stack, the registers under 32 bits are 4 bytes.
insert image description here

Assembly when pushing the stack : The first one is not mov, but movsx, which is a variant of the assembly language data transfer instruction MOV. Sign-extend, and transmit. That is, cosmetic enhancement.
insert image description here
The same is true: use float to pass parameters, and use double word length to go, there is no problem.
insert image description here
Summary :
So we are used to using int/double as the length inside the function body (Find_Max), and we can use char/short/float and other types when passing parameters.

Note:
The definition under 64-bit is very different from that under 32-bit.

Why align with 4 bytes: As
mentioned earlier, in the short integer type, the shaping promotion will occur in the process of pushing the stack, then the corresponding data should be extracted according to the corresponding method to get the corresponding data from the stack frame.

Mathematical understanding of _INTSIZEOF:

_INTSIZEOF(n) means: Calculate a minimum number x that satisfies x>=n && x%4==0, where n represents the value of sizeof(n). That is, the size of the type must be aligned to an integer multiple of n, and the minimum cannot be less than n.

4-byte alignment is chestnut:
n%4 == 0, then ret = n;
n %4 != 0 , then ret = (n+ 4 - 1)/4 *4;
(n+ 4 - 1)/4 - -> Assuming that n is 1 to 4, then the result of (n + 4 - 1)/4 is all 1, and then multiplied by the logarithm 4 is the minimum alignment number for 4 alignment. This can be controlled to 4个数值范围the 最小对齐倍数same value.

We observe that (n+ 4 -1)/4 *4, /4 is actually moving the binary sequence to the right, and *4 is moving the binary sequence to the left. This back and forth is actually to set the lowest two positions to 0, Then we can simplify it to:
(n+ 4 -1) & ~3 , which is the definition in the source code! !

#define _INTSIZEOF(n)\
  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )



Recognition of the two functions


Understanding of printf:
In the above example, the macro cannot determine the actual number of parameters and the type of the actual parameters , then in printf, there must be a method to determine the number of parameters and identify the type of parameters, which is actually %c, %d, %lf, where the number of % except %% can actually let us know the number of parameters, and %c, %d, actually indicate the corresponding type. Understanding


of the exec
insert image description here
series: In process control , it was described that there was actually only one system call execve, and other functions exec functions ultimately call the execve function, so how to realize the process from parameters lto vthis?
Answer:
In fact, until null is accessed, the number of parameters passed can be counted with a count, and the type is undoubtedly char*, we can use strlen to calculate how long it will take. (However, two char arrays are usually separated by more than 8 bytes)


Summarize

This concludes the list of parameters in this chapter. We may re-understand the function stack frame and publish a blog in the next issue. Here, the blogger wishes everyone a Happy New Year! ! !
一键三连一键三连一键三连一键三连一键三连

Guess you like

Origin blog.csdn.net/weixin_52344401/article/details/122764234