Dúvida sobre a função ZeroMemory()

Para adicionar comentários, por favor Faça o login ou registrar
Márcio Hermes
78
Márcio Hermes  

Olá a todos,

Uma dúvida que pode até ser trivial, mas como se trata de finanças, nada é trivial.

Pesquisando na comunidade, encontrei referências a função 'ZeroMemory()'. Não conhecia esta função, que é muito útil.

Então vamos analisar algumas possibilidades:

1º - um programador desenvolve uma função, em seu EA, que lança uma ordem de compra do tipo Limit. Nesta função TODAS as variáveis (MqlTradeRequest e MqlTradeResult) são LOCAIS, ou seja, todas as variáveis são declaradas e manipuladas dentro da função de compra. Logo, a cada chamada da função, serão declaradas novas variáveis para lançar uma nova ordem de compra do tipo Limit.

2º - outro programador desenvolve uma função, em seu EA, que também lança uma ordem de compra do tipo Limit. Porém nesta função, TODAS as variáveis (MqlTradeRequest e MqlTradeResult) são GLOBAIS, ou seja, todas as variáveis são apenas manipuladas dentro da função de compra. Logo, a cada chamada da função, estas variáveis serão "reutilizadas" para lançar uma nova ordem de compra do tipo Limit.

Observando estes dois casos, faz sentido usar a função 'ZeroMemory()' para o segundo caso, pois no primeiro caso as variáveis serão "descartadas" ao final da execução da função.

Então a dúvida é a seguinte:

- essa observação está correta?

- ou existe alguma limitação na linguagem que, mesmo usando variáveis locais (MqlTradeRequest e MqlTradeResult), se faz necessário usar a função 'ZeroMemory()' ?

Utilizo um EA que desenvolvi que lança ordens de compra e venda, tanto de forma automatizada quanto manual, porém, sempre utilizei variáveis locais (MqlTradeRequest e MqlTradeResult), e nunca tive nenhum problema com isso, ou pelo menos não percebi nenhum problema.

Abraço a todos.

Rogerio Giannetti Torres
3143
Rogerio Giannetti Torres  
Márcio Hermes:


Boa noite Márcio,

sua observação é correta,  toda chamada de função inicializa a variável local, mas, porém, contudo, desde que local não tenha sido declarada como static, nesse caso a variável mantém o valor da última vez que a função foi chamada.

Trader_Patinhas
1123
Trader_Patinhas  
Márcio Hermes:

Olá a todos,

Uma dúvida que pode até ser trivial, mas como se trata de finanças, nada é trivial.

Pesquisando na comunidade, encontrei referências a função 'ZeroMemory()'. Não conhecia esta função, que é muito útil.

Então vamos analisar algumas possibilidades:

1º - um programador desenvolve uma função, em seu EA, que lança uma ordem de compra do tipo Limit. Nesta função TODAS as variáveis (MqlTradeRequest e MqlTradeResult) são LOCAIS, ou seja, todas as variáveis são declaradas e manipuladas dentro da função de compra. Logo, a cada chamada da função, serão declaradas novas variáveis para lançar uma nova ordem de compra do tipo Limit.

2º - outro programador desenvolve uma função, em seu EA, que também lança uma ordem de compra do tipo Limit. Porém nesta função, TODAS as variáveis (MqlTradeRequest e MqlTradeResult) são GLOBAIS, ou seja, todas as variáveis são apenas manipuladas dentro da função de compra. Logo, a cada chamada da função, estas variáveis serão "reutilizadas" para lançar uma nova ordem de compra do tipo Limit.

Observando estes dois casos, faz sentido usar a função 'ZeroMemory()' para o segundo caso, pois no primeiro caso as variáveis serão "descartadas" ao final da execução da função.

Então a dúvida é a seguinte:

- essa observação está correta?

- ou existe alguma limitação na linguagem que, mesmo usando variáveis locais (MqlTradeRequest e MqlTradeResult), se faz necessário usar a função 'ZeroMemory()' ?

Utilizo um EA que desenvolvi que lança ordens de compra e venda, tanto de forma automatizada quanto manual, porém, sempre utilizei variáveis locais (MqlTradeRequest e MqlTradeResult), e nunca tive nenhum problema com isso, ou pelo menos não percebi nenhum problema.

Abraço a todos.

Prezado Marcio,

As variáveis globais (aquelas declaradas no início do programa, do lado de fora de qualquer função) e as variáveis locais estáticas (aquelas declaradas dentro de alguma função com o qualificador "static") residem no segmento de dados e são inicializadas com zero no início da execução do programa.

Já as variáveis locais não-estáticas (sem o qualificador "static" na declaração), estas NÃO são inicializadas automaticamente. Os valores iniciais que vêm nelas são indeterminados. Estes valores iniciais vão depender do que foi processado anteriormente nos endereços físicos de memória que elas estão ocupando, o que é absolutamente incerto, pois as variáveis locais não-estáticas residem na pilha do sistema, em endereços de memória compartilhados com outras variáveis locais de outras funções, além de serem também usados para armazenarargumentos de chamada função, endereços de retorno de função, etc. 

Ou seja, as variáveis locais não-estáticas nascem com valores indeterminados. Por isso, no caso de estruturas como MqlTradeRequest, se a estrutura não for global ou estática, vc PRECISA preencher TODOS os campos, caso contrário esses campos ficarão contendo valores indeterminados. A função ZeroMemory() pode ser útil para isso se a maioria dos campos for preenchida com zero, mas vc também pode optar por preencher explicitamente todos os campos da estrutura, em vez de chamar a ZeroMemory(). 

Se vc declarar a estrutura MqlTradeRequest como estática, ela preservará o valor atribuído na última vez que foi modificada. Isso pode ser útil quando vc envia as ordens sempre com os mesmos parâmetros, variando apenas o preço, por exemplo, pois nesse caso a estrutura já está pré-preenchida com a última ordem enviada e basta preencher os campos que precisarem ter valor diferente. 

Márcio Hermes
78
Márcio Hermes  
Rogerio Giannetti Torres:

Boa noite Márcio,

sua observação é correta,  toda chamada de função inicializa a variável local, mas, porém, contudo, desde que local não tenha sido declarada como static, nesse caso a variável mantém o valor da última vez que a função foi chamada.

Obrigado pelo retorno.

Não tenho o hábito de declarar variáveis como 'static', justamente para não ter complicações com valores antigos.

Porém, a observação que o  Trader_Patinhas ressaltou é válida:

 - "Se vc declarar a estrutura MqlTradeRequest como estática, ela preservará o valor atribuído na última vez que foi modificada. Isso pode ser útil quando vc envia as ordens sempre com os mesmos parâmetros, variando apenas o preço, por exemplo, pois nesse caso a estrutura já está pré-preenchida com a última ordem enviada e basta preencher os campos que precisarem ter valor diferente."

Márcio Hermes
78
Márcio Hermes  
Trader_Patinhas:

Prezado Marcio,

As variáveis globais (aquelas declaradas no início do programa, do lado de fora de qualquer função) e as variáveis locais estáticas (aquelas declaradas dentro de alguma função com o qualificador "static") residem no segmento de dados e são inicializadas com zero no início da execução do programa.

Já as variáveis locais não-estáticas (sem o qualificador "static" na declaração), estas NÃO são inicializadas automaticamente. Os valores iniciais que vêm nelas são indeterminados. Estes valores iniciais vão depender do que foi processado anteriormente nos endereços físicos de memória que elas estão ocupando, o que é absolutamente incerto, pois as variáveis locais não-estáticas residem na pilha do sistema, em endereços de memória compartilhados com outras variáveis locais de outras funções, além de serem também usados para armazenarargumentos de chamada função, endereços de retorno de função, etc. 

Ou seja, as variáveis locais não-estáticas nascem com valores indeterminados. Por isso, no caso de estruturas como MqlTradeRequest, se a estrutura não for global ou estática, vc PRECISA preencher TODOS os campos, caso contrário esses campos ficarão contendo valores indeterminados. A função ZeroMemory() pode ser útil para isso se a maioria dos campos for preenchida com zero, mas vc também pode optar por preencher explicitamente todos os campos da estrutura, em vez de chamar a ZeroMemory(). 

Se vc declarar a estrutura MqlTradeRequest como estática, ela preservará o valor atribuído na última vez que foi modificada. Isso pode ser útil quando vc envia as ordens sempre com os mesmos parâmetros, variando apenas o preço, por exemplo, pois nesse caso a estrutura já está pré-preenchida com a última ordem enviada e basta preencher os campos que precisarem ter valor diferente. 

Obrigado pelo retorno.

Com base no seu retorno, observei o código do EA desenvolvido, e há sim um padrão de preenchimento de ordens (desenvolvi, e não havia percebido este detalhe). Sempre uso ordens do tipo Limit (Buy ou Sell), onde o ativo é sempre o mesmo. O que muda é o comentário, o preço e o tipo da ordem.

É uma atualização que provavelmente irei fazer no futuro!

Olhando a própria Referência do Meta Editor (ao pressionar F1), observei que a função ZeroMemory() é utilizada em situações onde há laços de repetição (for(;;){}), o que faz todo o sentido. No meu caso, nunca tive problemas, pois nunca se fez necessário fazer uma iteração para selecionar alguma ordem específica (até ontem).

O EA sofreu um update e uma função de busca por uma ordem colocada "na pedra" se fez necessária, onde tive contato com a função ZeroMemory(). 

Somando o seu retorno e as observações da Referências MQL5, ficou claro e entendido.

Obrigado.

Trader_Patinhas
1123
Trader_Patinhas  
Márcio Hermes:

Obrigado pelo retorno.

Não tenho o hábito de declarar variáveis como 'static', justamente para não ter complicações com valores antigos.

Porém, a observação que o  Trader_Patinhas ressaltou é válida:

 - "Se vc declarar a estrutura MqlTradeRequest como estática, ela preservará o valor atribuído na última vez que foi modificada. Isso pode ser útil quando vc envia as ordens sempre com os mesmos parâmetros, variando apenas o preço, por exemplo, pois nesse caso a estrutura já está pré-preenchida com a última ordem enviada e basta preencher os campos que precisarem ter valor diferente."

Fiquei preocupado com esta frase que vc escreveu, pois me faz imaginar que há uma coisa importante que talvez vc ainda não tenha captado:

"Não tenho o hábito de declarar variáveis como 'static', justamente para não ter complicações com valores antigos."

Saiba que nas variáveis não-estáticas os valores NÃO são inicializados. Ou seja, elas podem conter inicialmente QUALQUER VALOR, pois elas ocupam endereços físicos na pilha do sistema, que podem ser compartilhados com outras variáveis locais de outras funções do seu programa, ou mesmo com variáveis usadas dentro das chamadas ao sistema operacional feitas pelo seu programa.

Portanto, se nas variáveis estáticas vc tem "complicações com valores antigos", nas não-estáticas vc tem as mesmas complicações com valores que podem ter sido deixados lá por outras funções do programa, ou até pelo sistema operacional.

No caso das estáticas, pelo menos vc sabe quais valores estão lá e pode deixar quietos os que ainda lhe servem. Já no caso das não-estáticas, NÃO DÁ PRA SABER O QUE ESTÁ LÁ e por essa razão vc TEM QUE INICIALIZAR TODOS OS CAMPOS.

No caso de variáveis locais declaradas dentro de um loop, se não houver nenhuma chamada de sistema operacional dentro do loop (imprimir na tela, ler arquivo, etc.), o conteúdo das variáveis declaradas dentro do loop geralmente não será alterado entre uma iteração e outra, e por essa razão, às vezes uma lógica de programação que se baseia na falsa premissa de que a variável nasce com determinado valor pode até funcionar .... mas note que eu disse "GERALMENTE" ... se o programa tiver threads concorrentes compartilhando a pilha, uma outra thread pode usar aquele mesmo endereço físico e alterar o valor armazenado ali ... o próprio compilador às vezes pode modificar a estrutura do seu loop para otimizar tempo de processamento ou memória e essas otimizações podem acabar fazendo com que o mesmo endereço físico fique sendo utilizado alternadamente por duas variáveis locais distintas ... enfim, em uma variável local não-estática declarada dentro de um loop, não é garantido que na iteração seguinte do loop vc encontrará lá o mesmo valor que deixou na iteração anterior.

 


Márcio Hermes
78
Márcio Hermes  
Trader_Patinhas:

Fiquei preocupado com esta frase que vc escreveu, pois me faz imaginar que há uma coisa importante que talvez vc ainda não tenha captado:

"Não tenho o hábito de declarar variáveis como 'static', justamente para não ter complicações com valores antigos."

Saiba que nas variáveis não-estáticas os valores NÃO são inicializados. Ou seja, elas podem conter inicialmente QUALQUER VALOR, pois elas ocupam endereços físicos na pilha do sistema, que podem ser compartilhados com outras variáveis locais de outras funções do seu programa, ou mesmo com variáveis usadas dentro das chamadas ao sistema operacional feitas pelo seu programa.

Portanto, se nas variáveis estáticas vc tem "complicações com valores antigos", nas não-estáticas vc tem as mesmas complicações com valores que podem ter sido deixados lá por outras funções do programa, ou até pelo sistema operacional.

No caso das estáticas, pelo menos vc sabe quais valores estão lá e pode deixar quietos os que ainda lhe servem. Já no caso das não-estáticas, NÃO DÁ PRA SABER O QUE ESTÁ LÁ e por essa razão vc TEM QUE INICIALIZAR TODOS OS CAMPOS.

No caso de variáveis locais declaradas dentro de um loop, se não houver nenhuma chamada de sistema operacional dentro do loop (imprimir na tela, ler arquivo, etc.), o conteúdo das variáveis declaradas dentro do loop geralmente não será alterado entre uma iteração e outra, e por essa razão, às vezes uma lógica de programação que se baseia na falsa premissa de que a variável nasce com determinado valor pode até funcionar .... mas note que eu disse "GERALMENTE" ... se o programa tiver threads concorrentes compartilhando a pilha, uma outra thread pode usar aquele mesmo endereço físico e alterar o valor armazenado ali ... o próprio compilador às vezes pode modificar a estrutura do seu loop para otimizar tempo de processamento ou memória e essas otimizações podem acabar fazendo com que o mesmo endereço físico fique sendo utilizado alternadamente por duas variáveis locais distintas ... enfim, em uma variável local não-estática declarada dentro de um loop, não é garantido que na iteração seguinte do loop vc encontrará lá o mesmo valor que deixou na iteração anterior.

 



Com base no seu retorno, vou explanar como o EA está desenvolvido para fins de alinhar a nossa conversa.

1º - o desenvolvimento do código está em dois arquivos tradicionais(.mq5 e .mqh). 

Observei que em vários exemplos aqui na comunidade, o pessoal gosta de deixar todo o código no próprio .mq5.

2º - no arquivo .mq5 é feito o #include e criado uma variável (ou um objeto) da classe incluída, como mostrado no exemplo de código abaixo:

#include  "TradeFIIDialog_V3.mqh"

 CTradeFIIDialog ExtDialog;
 CString ticker;

3º - logo, a partir dos eventos de OnTick(), OnTrade(), OnChartEvent(), e inclusive OnBookEvent(), a brincadeira começa. Inclusive são gerados eventos de botões.

4º - feito este preâmbulo, vamos aos fatos. A função que está incluída logo abaixo, contém as tradicionais variáveis do tipo MqlTradeRequest e MqlTradeResult.

Ambas variáveis MqlTradeRequest e MqlTradeResult  são declaradas de foma local, ou seja, dentro da função. Se realmente entendi sua explanação, e se estamos "falando a mesma língua", seria necessário então usar a função  ZeroMemory(), antes de "montar" a ordem de compra, para garantir que todas as variáveis sejam inicializadas com valores "padrão". Se não entendi de forma correta, aponte-me o erro, por favor.

Está postado toda a função:

double CTradeFIIDialog::TradeBuyOnBID(void){
      
   string_volume.Assign(m_edit_volume_cotas_negociar.Text()); // coleta o volume digitado pelo usuário
   
   double volume_digitado = STD(string_volume.Str()); // converte o volume de String para double
   
   double volume_livre_para_comprar = STD(m_edit_cotas_livres_para_comprar.Text());
   
   if(volume_livre_para_comprar <= 0){
       MessageBox("Não há COTAS LIVRES para lançar a ordem de COMPRA.", "Erro: Cotas Insuficientes",0);
         
      // limpar os valores
      string_volume.Clear(); // limpa os valores da variável global           
      return -1; // retorna um valor padrão
      
   }else if(volume_digitado > volume_livre_para_comprar){
       MessageBox("O volume de cotas digitado é maior que o volume "
                        + "de cotas disponíveis para comprar.\n"
                        + "Não é possível lançar a ordem de COMPRA."
                        , "Erro: Cotas Insuficientes",0);
            
      // limpar os valores
      string_volume.Clear(); // limpa os valores da variável global           
      return -1; // retorna um valor padrão
        
   }else if(volume_digitado <= 0){
      volume_digitado = volume_livre_para_comprar;
      
   }
      
//--- declare and initialize the trade request and result of trade request
   MqlTradeRequest request={0};
   MqlTradeResult  result={0};
//--- parameters of request
   request.action    = TRADE_ACTION_PENDING;        // ordem pendente
   request.symbol    = Symbol();                    // symbol (ticker)
   request.volume    = volume_digitado;             // volume do tipo double
   request.type      = ORDER_TYPE_BUY_LIMIT;        // ordem de compra do tipo limit
   request.type_time = ORDER_TIME_DAY;              // válido para o dia
   request.price     = SymbolInfoDouble(_Symbol,SYMBOL_BID);                       // price for opening
   request.magic     = MAGIC_NUMBER;  
   request.type_filling =  ORDER_FILLING_RETURN;                 
   request.comment   = "Trade Buy On BID: BOTÃO COMPRA BID";
   
   //limpar valor
   string_volume.Clear();  
   m_edit_volume_cotas_negociar.Text(""); 
   
             
//--- send the request
   if(OrderSend(request,result)){
      PrintFormat("OrderSend Success! retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order); 
       
      // ordem enviada
      return result.price;
   }else{
      PrintFormat("OrderSend error %d",GetLastError());     // if unable to send the request, output the error code
     
      ResetLastError();   
      // erro no envio
      return -1;
   }
  
   return -1;
} // end função double CTradeFIIDialog::TradeBuyOnBID(void)


Já nesta função, as variáveis MqlTradeRequest e MqlTradeResult também são declaradas de forma local, dentro da função, porém como se trata de uma iteração, onde há uma busca por uma ordem em específico, se faz necessário a utilização da função  ZeroMemory() para garantir que todas as variáveis sejam inicializadas com o valor "padrão", antes da próxima iteração. Segue código abaixo:

void CTradeFIIDialog::OnButtonCancelarOrdemDeVenda(void){
   bool isPressed = m_button_automatico_habilita_SELL.Pressed();
   
   if(isPressed){
      m_button_automatico_habilita_SELL.Color(clrBlack);
      m_button_automatico_habilita_SELL.ColorBackground(clrDarkGray);
      m_button_automatico_habilita_SELL.ColorBorder(clrDarkGray); 
      m_button_automatico_habilita_SELL.Text("AUTO SELL LIMIT OFF");
      m_button_automatico_habilita_SELL.Pressed(false);      
      m_edit_automatico_valor_SELL.ReadOnly(false);
   }     
   
   int ordersTotal = OrdersTotal();
    
   MqlTradeRequest request = {0};
   MqlTradeResult result = {0};
   
   for(int i = ordersTotal - 1; i >= 0; i--) {
     if(OrderGetTicket(i) > 0) {
       if((OrderGetString(ORDER_SYMBOL) == Symbol())
          && (OrderGetInteger(ORDER_MAGIC) == MAGIC_NUMBER)
          && (OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_SELL_LIMIT)) {
         
         request.action = TRADE_ACTION_REMOVE;
         request.order = OrderGetTicket(i);
         if(!OrderSend(request, result)) {
           PrintFormat("CheckCloseAll - remove order sell limit - ", 
                      __FUNCTION__, ": OrderSend error %d", GetLastError());
           ResetLastError();            
         }      
         ZeroMemory(request);
         ZeroMemory(result);
       }
     }
   }
   
   if(isPressed){
      m_edit_automatico_valor_SELL.ReadOnly(true);
      m_button_automatico_habilita_SELL.Pressed(true);
      m_button_automatico_habilita_SELL.Color(clrWhite);
      m_button_automatico_habilita_SELL.ColorBackground(clrRed);
      m_button_automatico_habilita_SELL.ColorBorder(clrRed);
      m_button_automatico_habilita_SELL.Text("AUTO SELL LIMIT ON");
   }
}

Grande abraço.

Trader_Patinhas
1123
Trader_Patinhas  
Márcio Hermes:


Com base no seu retorno, vou explanar como o EA está desenvolvido para fins de alinhar a nossa conversa.

1º - o desenvolvimento do código está em dois arquivos tradicionais(.mq5 e .mqh). 

Observei que em vários exemplos aqui na comunidade, o pessoal gosta de deixar todo o código no próprio .mq5.

2º - no arquivo .mq5 é feito o #include e criado uma variável (ou um objeto) da classe incluída, como mostrado no exemplo de código abaixo:

3º - logo, a partir dos eventos de OnTick(), OnTrade(), OnChartEvent(), e inclusive OnBookEvent(), a brincadeira começa. Inclusive são gerados eventos de botões.

4º - feito este preâmbulo, vamos aos fatos. A função que está incluída logo abaixo, contém as tradicionais variáveis do tipo MqlTradeRequest e MqlTradeResult.

Ambas variáveis MqlTradeRequest e MqlTradeResult  são declaradas de foma local, ou seja, dentro da função. Se realmente entendi sua explanação, e se estamos "falando a mesma língua", seria necessário então usar a função  ZeroMemory(), antes de "montar" a ordem de compra, para garantir que todas as variáveis sejam inicializadas com valores "padrão". Se não entendi de forma correta, aponte-me o erro, por favor.

Está postado toda a função:


Já nesta função, as variáveis MqlTradeRequest e MqlTradeResult também são declaradas de forma local, dentro da função, porém como se trata de uma iteração, onde há uma busca por uma ordem em específico, se faz necessário a utilização da função  ZeroMemory() para garantir que todas as variáveis sejam inicializadas com o valor "padrão", antes da próxima iteração. Segue código abaixo:

Grande abraço.

Olá @Márcio Hermes

Vendo seu código constato que na verdade vc não precisa usar ZeroMemory() em lugar nenhum.

Na função TradeBuyOnBID não precisa porque vc inicializa a estrutura "request" com zeros na declaração e a usa logo em seguida.

Na função OnButtonCancelarOrdemDeVenda também não precisa porque, segundo a documentação, a ação TRADE_ACTION_REMOVE requer o preenchimento somente dos campos "action" e "order" (os demais campos serão ignorados, não importa como estiverem preenchidos) e você está preenchendo esses dois campos.

Márcio Hermes
78
Márcio Hermes  
Trader_Patinhas:

Olá @Márcio Hermes

Vendo seu código constato que na verdade vc não precisa usar ZeroMemory() em lugar nenhum.

Na função TradeBuyOnBID não precisa porque vc inicializa a estrutura "request" com zeros na declaração e a usa logo em seguida.

Na função OnButtonCancelarOrdemDeVenda também não precisa porque, segundo a documentação, a ação TRADE_ACTION_REMOVE requer o preenchimento somente dos campos "action" e "order" (os demais campos serão ignorados, não importa como estiverem preenchidos) e você está preenchendo esses dois campos.

Obrigado pelo retorno.

Agradeço pela explicação e pela paciência. 

Grande abraço e bons negócios para todos nós.

Para adicionar comentários, por favor Faça o login ou registrar