Variable parameter learning notes

xiaoxiao2021-03-06  36

Preface: This article has been largely adapted from the "Usage of variable parameters in the C language" from netizens, and first expressed sincere payment and gratitude to this predecessor before the text.

First, what is a variable parameter

We sometimes encounter some variable variable functions in C language programming, such as a Printf () function, its function prototype:

INT Printf (const char * format, ...);

In addition to having a parameter Format fixation, the number and type of the parameters of the back and the number of parameters are variable (three points "..." do parameter placeholders), and can have the following forms when they actually call: Printf ("%) D ", i);

Printf ("% s", s);

Printf ("THE NUMBER IS% D, String IS:% S", I, S);

These things have been familiar with everyone. But how to write variable parameters and how the function compiler of these variable parameters is implemented, but this problem has been plaguing me for a long time. This article conducts some discussions on this issue, hoping to have some help to everyone.

Second, write a simple variable parameter C function

First look at the example. This function has at least one integer parameter, which is the number of placeholders ..., indicating that the number of back parameters is uncertain. In this example, all input parameters must be integers, the functionality of the function is just the value of all parameters.

The function code is as follows:

// Sample Code 1: Use of variable parameter functions

#include "stdio.h"

#include "stdarg.h"

Void Simple_va_fun (int start, ...)

{

VA_LIST ARG_PTR;

Int Nargvalue = start;

INT Nargcout = 0; // Number of variable parameters

VA_START (arg_ptr, start); // determines the memory start address of the change in parametery at the starting point of the address of the fixed parameter.

DO

{

Nargcout;

Printf ("THE% D TH ARG:% D / N", NARGCOUT, NARGVALUE); / / Output Values ​​of each parameter

Nargvalue = VA_ARG (arg_ptr, int); // Get the value of the next variable parameter

} while (NargValue! = -1);

Return;

}

Int main (int Argc, char * argv [])

{

SIMPLE_VA_FUN (100, -1);

SIMPLE_VA_FUN (100, 200, -1);

Return 0;

}

Let's explain these code

As can be seen from this function, we should use variable parameters to follow these steps:

(1) Since the following macros will be used in the program:

Void va_start (VA_LIST ARG_PTR, PREV_PARAM);

TYPE VA_ARG (VA_LIST ARG_PTR, TYPE);

Void va_end (VA_LIST ARG_PTR);

VA here is the meaning of variable-argument (variable parameters).

These macros are defined in stdarg.h, so the program for variable parameters should contain this header file.

(2) First define a VA_LIST type variable first, this is Arg_Ptr, this change

The amount is a pointer to the parameter address. Because the address of the parameter is obtained, the value of the parameter can be obtained by combining the type of parameters.

(3) The variable arg_ptr defined in (2) (2) is then used to initialize (2), and the second parameter of this macro is the previous parameter of the variable parameter list, that is, the last fixed parameter.

⑷ Then use the VA_ARG macros to return Arg_PTR to the address of the variable parameter. After this address, the value of the parameter can be obtained.

⑸ Set the end condition, the condition here is whether it is determined whether the parameter value is -1. Note that the called function is called when the call does not know the correct number of variable parameters, and the programmer must specify the end condition in the code. As for why it does not know the number of parameters, readers will naturally understand after reading the internal implementation mechanisms of these macros. (2) Processing of variable parameters in the compiler

We know that VA_START, VA_ARG, VA_END are defined in stdarg.h, because 1) The hardware platform is different from the compiler, so the defined macro is different. Let's take a look at the VC 6.0 STDARG. The code in the h (the path to the file is VC installation directory /VC98/include/stdarg.h)

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)

Below we explain the meaning of these codes:

1. First, the VA_LIST is defined as a char * because the character pointer type can be used to store the memory unit address on the PC we currently used. And VA_LIST is defined as a void * on the machine.

2, definition _intsizeof (n) is mainly for some system that requires memory, the purpose of this macro is to get the actual memory size of the last fixed parameter. In my machine, use the SIZEOF operator directly to instead, there is no impact on the running structure of the program. (Later will see my own implementation).

3, VA_START is defined as & V _INTSIZEOF (V), where & V is the start address of the last fixed parameter, and then the initial memory address of the first variable parameter is obtained after the actual occupancy size. So after we run Va_Start (ap, v), the AP points to the first variable parameter in the memory address, with this address, and later things are simple.

Here you must know two things:

(1) On the INTEL Windows machine, the direction of the function stack is down, the memory address of the top pointer is lower than the hook pointer, so the data of the advanced stack is stored at the high address of the memory.

(2) In most of the VC, in most C compilers, by default, the order of parameters inrest is from right to left, and therefore, the memory model after the parameter is inrest, as shown below: The address of the last fixed parameter Located under the first variable parameter and is continuously stored.

| -------------------------- |

| The last variable parameter | -> High internal memory address

| -------------------------- |

...................

| -------------------------- |

Native N variable parameters | -> VA_ARG (arg_ptr, int), the place refers to the arg_ptr,

| | Ie the address of the nth variable parameter.

| --------------- |

..............................

| -------------------------- |

First variable parameters | -> VA_START (arg_ptr, start) After the place refers to the arg_ptr

| | That is, the address of the first variable parameter | --------------- |

| ------------------------ - |

| | |

| Last fixed parameter | -> start address of Start

| -------------- - | .................

| -------------------------- |

| | |

| --------------- | -> Low Call Address

(4) VA_ARG (): With the good foundation of VA_START, we have obtained the address of the first variable parameter, the task in the VA_ARG () is the value of this parameter according to the specified parameter type, and adjust the pointer to The start address of the next parameter.

Therefore, now look at the realization of VA_ARG () should have a number in mind:

#define va_arg (AP, T) (* (t *) ((AP = _INTSIZEOF (T)) - _INTSIZEOF (T)))))

This macro has made two things.

1 Mandatory type conversion of the parameter address with the type name entered by the user, get the value required by the user

2 Calculate the actual size of this parameter, adjust the needle to the end of this parameter, that is, the first address of the next parameter to subsequent processing.

(5) Interpretation of VA_END macro: The X86 platform is defined as AP = (char *) 0; making the AP no longer point to the stack, but is the same as NULL. Some direct definitions ((void *)), so the compiler will not Generate code for VA_END, such as the GCC's X86 platform in Linux, is defined. Everyone should pay attention to a problem: Since the address of the parameter is used for the VA_START macro, the parameters cannot be declared as a register variable or as a function or array type. About VA_START , VA_ARG, VA_END's description is these, we have to pay attention to the definition of different operating systems and hardware platforms, but the principle is similar.

(3) Problems that variable parameters should pay attention to in programming

Because VA_START, VA_ARG, VA_END, etc., it is very stupid, the type and number of variable parameters are completely controlled by program code in this function, which does not intelligently identify the number and type of different parameters. Some people will Q: Is the intelligent identification parameter in PRINTF? That's because the function printf is the type of parameter from the fixed parameter format string, and then calls VA_ARG to obtain variable parameters. That is, you want to implement it. Intelligent identification variable parameters are to be implemented by judging in their own programs. For example, a PrintF may be implemented in the 7.3 section of C's Classic Textbook "The C Programming Language", due to space The reason is no longer described here.

(4) Small knot:

1. The three macros of the standard C library only uses the memory address of each parameter in the variable parameter list, the compiler is not known for the actual number of parameters.

2. In the code of the actual application, the programmer must consider the number of parameters, such as

(1) Setting the flag in fixed parameters - The Printf function is to use this method. There is also an example behind.

(2) When a special end tag is set in advance, it is said to be multi-input a variable parameter. When calling, the value of the last variable parameter is set to this special value, and in the function body determines whether or not the parameters are reached according to this value. end. The code in front of this article is to adopt this approach.

Regardless of which approach is used, the programmer should tell the caller to its own agreement in the document.

3. The key points for realizing variable parameters is to find the address of each parameter, and obtain the following factors by the following factors:

1 Function stack growing direction

2 parameter in the stack order

3CPU alignment

4 Expression of memory address

Combined with the source code, we can see that the implementation of VA_LIST is determined by 4, and _intsizeof (n) is determined by 3 decisions, he and the 12 also determine the implementation of Va_Start, and finally the existence of VA_END is a good programming style. Reflect, the pointer that will no longer be used as NULL, which prevents future misoperation. 4. After obtaining the address, the type of parameters will be combined, and the programmer can handle the parameters correctly. Understand the above points, I believe that a slightly experienced reader can write out the implementation of the machine. Below is an example

(5) Extended - the function of simply variable parameters.

Below is an implementation of a simple printf function, referring to the example of page 156 in , the reader can comment with the code in the book and this article.

#include "stdio.h"

#include "stdlib.h"

Void MyPrintf (Char * fmt, ...) // A simple similar to the Printf implementation, // parameter must be int type

{

Char * Parg = NULL; / / is equivalent to the original VA_LIST

Char C;

PARG = (char *) & fmt; // Notice Don't write to p = fmt !!

Parg = sizeof (fmt); / / equivalent to the original VA_START

DO

{

C = * fmt;

IF (c! = '%')

{

PUTCHAR (C); // Output character as original

}

Else

{

/ / Press Format Character Output Data

Switch (* fmt)

{

Case 'd':

Printf ("% D", * ((int *) PARG);

Break;

Case 'x':

Printf ("% # x", * ((int *) pag);

Break;

DEFAULT:

Break;

}

Parg = sizeof (int); // is equivalent to the original VA_ARG

}

FMT;

} while (* fmt! = '/ 0');

Parg = null; // Is equivalent to VA_END

Return;

}

Int main (int Argc, char * argv [])

{

INT I = 1234;

INT J = 5678;

MyPrintf ("THE FIRST TEST: I =% D / N", I, J);

MyPrintf ("The SECEND TEST: I =% D;% X; J =% D; / N", I, 0XABCD, J);

System ("pause");

Return 0;

}

The machine execution in Intel Win2K VC6 is as follows:

THE FIRST TEST: I = 1234

THE SECEND TEST: I = 1234; 0xAbcd; J = 5678;

转载请注明原文地址:https://www.9cbs.com/read-61811.html

New Post(0)