Só que nem sempre desejamos iniciar o array com o valor 0, e se você quiser que todos itens armazenem o valor 5? Aqui vem a pegadinha, alguém poderia surgir com a idéia: Simples, uso memset! Ok, pode funcionar, caso você esteja utilizando um array de char, que acaba sendo um byte na maioria dos casos. Mas se seu array for um array de inteiros, aí a coisa muda de figura. Vejamos o que o manpage do Linux tem a dizer sobre o memset(Tomei a liberdade de traduzir de forma que faça mais sentido em português):
NOME
memset - Preenche uma região de memória com um valor(byte) constante.
SINOPSE
#include < string.h >
void *memset(void *s, int c, size_t n);
DESCRIÇÃO
A função memset() preenche os primeiros n bytes da área de memória apontada por s com o valor constante do byte c.
Lembrete: Embora o parametro c seja um inteiro somente o primeiro byte, o de menor ordem, será utilizado e esse byte será convertido para um byte sem sinal, unsigned byte. Então mesmo que você tente passar o valor -1 ele será visto como 255 internamente à memset, logicamente o resultado real vai depender do tipo do container que você estiver utilizando.
Ok, e daí? Daí que a função memset opera copiando bytes e um inteiro (int) ocupa, normalmente, quatro bytes na memória. Então o que acontece quando você tenta copiar um valor diferente de zero para um array de inteiro é o seguinte:
Imagine que as caixinhas abaixo são os bytes na memória que correspondem a um inteiro (Estou assumindo um cpu little endian - se não sabe do que se trata espere um post futuro ou procure no google!) Imagine também que o valor zero está armazenado no inteiro, os números sobre as caixinhas indicam o endereço base começando em 0:
0[00000000]1[00000000]2[00000000]3[00000000]
Este é um int na memória com o valor 0, um inteiro com o valor 42 seria armazenado da seguinte forma, em binário:
0[00101010]1[00000000]2[00000000]3[00000000]
Quando você usa memset em um array de char, tudo bem, o código funciona pois, um char na maioria dos casos ocupa um byte na memória ou seja um único quadrinho:
0
[00000000]
Em um array de 5 elementos:
char cArray[5];
memset(cArray,5,sizeof(char)*5);
Tudo funciona como esperado:
endereço: 0
[00000101] indice: 0
endereço: 1
[00000101] indice: 1
endereço: 2
[00000101] indice: 2
endereço: 3
[00000101] indice: 3
endereço: 4
[00000101] indice: 4
Cada elemento contem o valor 5 como esperado, agora quando você tentar executar um código como:
int iArray[5];
memset(iArray,5,sizeof(int)*5);
Eis o que vai acontecer:
0[00000101]1[00000101]2[00000101]3[00000101] indice: 0
4[00000101]5[00000101]6[00000101]7[00000101] indice: 1
8[00000101]9[00000101]10[00000101]11[00000101] indice: 2
12[00000101]13[00000101]14[00000101]15[00000101] indice: 3
16[00000101]17[00000101]18[00000101]19[00000101] indice: 4
Cada índice vai conter o valor: "00000101000001010000010100000101" cuja representação em decimal é 84215045, um bocado longe do que era desejado. Para reforçar o assunto vamos utilizar outro valor e ver o que acontece. Imagine o valor 255 que é o maior valor que um byte sem sinal pode representar, lembrando que o byte, sendo composto por oito bits, pode representar 256 valores distintos, um intervalo de 0 a 255 normalmente. O valor 255 em hexa 0xFF pode ser armazenado com folga em um int, mesmo em um int com sinal. O valor 255 não pode ser representado em um char com sinal já que um signed char utiliza o primeiro bit para indicar sinal. Até aqui tudo bem, qualquer programador com meio neurônio sabe disso, mas seria razoável alguém cometer o erro do memset contando com o fato de que um int suporta um byte com sobra. A mesma operação usando o valor 255 teria como resultado o seguinte:
0[11111111]1[11111111]2[11111111]3[11111111] indice: 0
4[11111111]5[11111111]6[11111111]7[11111111] indice: 1
8[11111111]9[11111111]10[11111111]11[11111111] indice: 2
12[11111111]13[11111111]14[11111111]15[11111111] indice: 3
16[11111111]17[11111111]18[11111111]19[11111111] indice: 4
Cada índice contem o valor “11111111111111111111111111111111” que em decimal é “4294967295” ou em hexa 0xFFFFFFFF, agora se nosso array tiver sido declarado como int, o que por default faz dele um signed int, teremos o valor -1 armazenado em cada um dos 5 elementos do array, o que com certeza é uma surpresa nada agradável para um programador olhando um dump de memória ou tentando debugar o código, afinal se eu copiei o valor 255 em um inteiro como é que posso ter -1? E não para por ai, para arrays do tipo float a coisa é ainda pior, floats em C são codificados de acordo com o padrão IEEE 754, e o único valor que tem uma correspondência binária entre valores inteiros e valores de ponto flutuante é o valor/número zero, todos os outros diferem, e muito, em sua representação. Sendo assim o único uso de memset com floats será para atribuir o valor zero, qualquer outra tentativa resultará em um valor errado e dificílimo de ser entendido. Mesmo sabendo o padrão binário armazenado na região de memória é complicado definir o valor codificado como ponto flutuante no olhometro, ao contrário de valores codificados como inteiro. Por exemplo, o valor decimal 457 é armazenado como inteiro no seguinte padrão “111001001” se somar um, ele passa a valer, logicamente, 458 e a representação binária é “111001010”, ambos facilmente legíveis, basta uma operação mental ou canetal de troca de base para saber o valor. O maior problema que poderia ser encontrado seria eventuais diferenças entre maquinas big endian e little endian. Agora, usando armazenamento em variáveis de ponto flutuante o valor 457 é codificado em binário como “1000011111001001000000000000000” já o valor 458 é representado como “1000011111001010000000000000000” note que embora seja possível encontrar o valor aí, não é nada human-readable. Logo evite codificar valores de ponto flutuante na mão ;) É claro que você pode codificar na unha o valor desejado, mas não existe nenhuma situação razoável onde este truque faça sentido. Então, para finalizar, fique atento aos atalhos que algumas funções da biblioteca padrão, como a memset, parecem oferecer pois, você pode acabar mandando seu míssil teleguiado pro lugar errado devido a um erro de calculo astronômico causado por valores espúrios que podem resultar de um erro tão simples quanto esse. Abaixo um código que demonstra exatamente o comportamento que foi descrito no artigo:
#include < stdio.h >
#include < stdlib.h >
#include < string.h >
int main()
{
int iArray[5];
int i;
printf("Valores no array(lixo):\n");
for (i = 0; i < 5; ++i)
{
printf("%d ", iArray[i]);
}
printf("\n");
memset(iArray,0,sizeof(int)*5);
printf("Valores apos o memset(com 0): \n");
for (i = 0; i < 5; ++i)
{
printf("%d ", iArray[i]);
}
printf("\n");
memset(iArray,5,sizeof(int)*5);
printf("Valores apos o memset usando o valor 5: \n");
for (i = 0; i < 5 ++i)
{
printf("%d ", iArray[i]);
}
printf("\n");
return 0;
}
A primeira linha da saida depende do ambiente uma vez que depende do que estiver
na memória no momento da execução, porem, no geral, a saida será parecida com:
187-24-217-3:~ killocanmhc$ ./teste
Valores no array(lixo):
0 0 0 0 -1881139893
Valores apos o memset(com 0):
0 0 0 0 0
Valores apos o memset usando o valor 5:
84215045 84215045 84215045 84215045 84215045
That's all folks!
5 comentários:
Boa rapaz!
Ai cara estou precisando fazer um trabalho sobre arquivo em C e umas das perguntas é apagar o conteúdo de arquivo C (Obs: Nao é a apagar o arquivo e sim só o conteúdo)
Certo. Mas e se eu quisesse armazenar um valor (x+1) em um vetor de n elementos - sendo que x inicia com 1?
Que tenso, vou tomar mais cuidado ao usar memset, vlw xD
Muito bom o blog.
Excelente artigo. Deu pra entender perfeitamente sem dificuldades tudo.
Postar um comentário