Diretório de artigos
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?
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:
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:
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
quadro de pilha de código:
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.
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.
O mesmo é verdade: use float para passar parâmetros e use double word length para ir, não há problema.
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
: 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 l
para v
isso?
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! ! !
一键三连一键三连一键三连一键三连一键三连