[Linguagem C] Lista de parâmetros variáveis


prefácio

A lista de parâmetros de variáveis ​​é usada como um array.Se você estudou o frame da pilha de funções, você pode descobrir que ele é realmente acessado continuamente em um espaço definido pela área da pilha, mas não suporta acesso direto na parte do meio.


Isenção de responsabilidade: Todos os testes abaixo foram feitos em x86, vs2013.

1. O que é uma lista de parâmetros variáveis?


insira a descrição da imagem aqui
Na primeira lição da nossa linguagem C inicial, fomos expostos a listas de parâmetros variáveis. No processo de printf, geralmente podemos passar um grande número de parâmetros para serem impressos, mas não sabemos como ele faz isso. Hoje Vamos analisá-lo.



Segundo, como usar a lista de parâmetros variáveis


Primeiro temos que introduzir o arquivo de cabeçalho do windows.he
depois temos que introduzir as seguintes macros. Aqui descrevemos brevemente suas funções, e haverá uma explicação detalhada mais tarde, aqui é para a conveniência de todos começarem.

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

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

1._ADDRESSOF : Pega o endereço da variável de entrada.

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




2. _INTSIZEOF : Esta função de macro é arredondar o tipo de n para um múltiplo de 4.

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

O seguinte trecho de código explica:

#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;
}

resultado:
insira a descrição da imagem aqui


3. __crt_va_start_a : Pegue o endereço da variável v e converta-o para char* e então aponte para o tipo v e conte-o, ou seja, encontre a variável na primeira lista de parâmetros de variável!

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



4. __crt_va_arg : Aponte ap para o próximo local a ser acessado antecipadamente e retorne o conteúdo atualmente acessado. Observe que ap após += aponta para o próximo endereço a ser acessado, mas o conteúdo retornado é o atual.

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



5. __crt_va_end : Defina ap para NULL.

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

A seguir, veremos as seguintes definições.
#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end

Teste: encontre o maior conjunto de dados que não está armazenado na matriz e retorne-o.

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;
}

resultado:
insira a descrição da imagem aqui


3. Análise aprofundada de macros


Embora o espaço de endereço do processo no Linux seja organizado de alto a baixo, mas como a memória no VS é de byte baixo a byte alto, nossa pilha será diferente daquela desenhada no Linux, mas todas são direcionadas para Extends na direção de endereços baixos. Isso é para todos entenderem.

Diagrama esquemático do espaço de endereço do processo Linux: diagrama esquemático do
insira a descrição da imagem aqui
quadro de pilha de código:
insira a descrição da imagem aqui


conversão de tipo implícito


Por exemplo, quando executamos o código a seguir, quando passamos parâmetros no tipo char, mas o corpo da função ainda é obtido em passos int, haverá algum erro neste momento?

#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");
}

Resposta:
Não, porque os parâmetros são passados ​​por registradores ao empurrar a pilha, os registradores abaixo de 32 bits são de 4 bytes.
insira a descrição da imagem aqui

Assembly ao empurrar a pilha : O primeiro não é mov, mas movsx, que é uma variante da instrução de transferência de dados da linguagem assembly MOV. Sinal-estender e transmitir. Ou seja, aprimoramento cosmético.
insira a descrição da imagem aqui
O mesmo é verdade: use float para passar parâmetros e use double word length para ir, não há problema.
insira a descrição da imagem aqui
Resumo :
Estamos acostumados a usar int/double como comprimento dentro do corpo da função (Find_Max), e podemos usar char/short/float e outros tipos ao passar parâmetros.

Nota:
A definição em 64 bits é muito diferente daquela em 32 bits.

Por que o alinhamento de 4 bytes: Como
mencionado anteriormente, no tipo inteiro curto, a promoção de modelagem ocorrerá no processo de empurrar a pilha, portanto, para obter os dados correspondentes do quadro de pilha, eles devem ser extraídos de acordo com o método correspondente.

Compreensão matemática de _INTSIZEOF:

_INTSIZEOF(n) significa: Calcular um número mínimo x que satisfaça x>=n && x%4==0, onde n representa o valor de sizeof(n). Ou seja, o tamanho do tipo deve ser alinhado a um múltiplo inteiro de n e o mínimo não pode ser menor que n.

O alinhamento de 4 bytes é castanho:
n%4 == 0, então ret = n;
n %4 != 0 , então ret = (n+ 4 - 1)/4 *4;
(n+ 4 - 1)/4 - - > Assumindo que n é 1 a 4, então o resultado de (n + 4 - 1)/4 é todo 1, e então multiplicado pelo logaritmo 4 é o número mínimo de alinhamento para 4 alinhamento. Isso pode ser controlado para 4个数值范围o 最小对齐倍数mesmo valor.

Observamos que (n+ 4 -1)/4 *4, /4 está na verdade movendo a sequência binária para a direita e *4 está movendo a sequência binária para a esquerda. Esse vai e vem na verdade para definir as duas posições mais baixas para 0, então podemos simplificar para:
(n+ 4 -1) & ~3 , que é a definição no código-fonte! !

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



Reconhecimento das duas funções


Compreensão de printf:
No exemplo acima, a macro não pode determinar o número real de parâmetros e o tipo dos parâmetros reais , portanto, em printf, deve haver um método para determinar o número de parâmetros e identificar o tipo de parâmetros, que é na verdade %c, %d, %lf, onde o número de % exceto %% pode nos informar o número de parâmetros, e %c, %d, na verdade indica o tipo correspondente. Compreensão


da série exec
insira a descrição da imagem aqui
: No controle de processos , foi descrito que na verdade havia apenas uma chamada de sistema execve, e outras funções exec chamam a função execve, então como realizar o processo de parâmetros lpara visso?
Resposta:
Na verdade, até que null seja acessado, o número de parâmetros passados ​​pode ser contado com uma contagem, e o tipo é sem dúvida char*, podemos usar strlen para calcular quanto tempo demora. (No entanto, dois arrays de caracteres geralmente são separados por mais de 8 bytes)


Resumir

Isso conclui a lista de parâmetros neste capítulo. Na próxima edição, podemos re-entender o quadro de pilha de funções e publicar um blog. Aqui, o blogueiro deseja a todos um Feliz Ano Novo! ! !
一键三连一键三连一键三连一键三连一键三连

Acho que você gosta

Origin blog.csdn.net/weixin_52344401/article/details/122764234
Recomendado
Clasificación