Escrevendo o caos em C

Existem formas simples de se descrever o caos. Este artigo mostra alguns pequenos exemplos de como provocar o caos em C.

[ Hits: 41.908 ]

Por: Ronaldo Faria Lima em 21/06/2004


Longjump: O goto não local



Existe, ainda, algo que consegue ser pior que o goto: o long jump. O long jump faz com que a pilha seja retornada a um estado salvo anteriormente, não importando o quanto você tenha empilhado. Observe o código abaixo (este código compila e funciona adequadamente):

#include <setjmp.h>
#include <stdio.h>

static void func1 (jmp_buf mark);
static void func2 (jmp_buf mark);
static void func3 (jmp_buf mark);
static void func4 (jmp_buf mark);
static void func5 (jmp_buf mark);

int main (void)
{
   jmp_buf mark;
   int rc;

   rc = setjmp (mark);
   if (!rc)
      func1 (mark);
   printf ("Program ended\n");
   return 0;
}

static void func1 (jmp_buf mark)
{
   for (;;) {
      func2 (mark);
   }
}
static void func2 (jmp_buf mark)
{
   for (;;) {
      func3 (mark);
   }
}
static void func3 (jmp_buf mark)
{
   for (;;) {
      func4 (mark);
   }
}
static void func4 (jmp_buf mark)
{
   for (;;) {
      func5 (mark);
   }
}
static void func5 (jmp_buf mark)
{
   longjmp (mark, -1);
}

As funções vão sendo empilhadas até a chamada do fatídico longjmp dentro de func5. À primeira vista, os loops infinitos dão a entender que este programa deveria entrar em um estado de loop do qual não poderia sair. Porém, nenhum dos loops chega, sequer, a executar sua primeira interação. Ao encontrar com o longjmp em func5, a pilha é retornada ao estado anterior e o programa termina com sucesso.

Dentro do debugger, este é o estado da pilha imediatamente antes da chamada do longjmp dentro de func5:

func5(int * 0x0012ff40) line 48
func4(int * 0x0012ff40) line 43 + 9 bytes
func3(int * 0x0012ff40) line 37 + 9 bytes
func2(int * 0x0012ff40) line 31 + 9 bytes
func1(int * 0x0012ff40) line 25 + 9 bytes
main() line 17 + 9 bytes

Logo após a chamada do longjmp, este é o novo estado da pilha:

main() line 15 + 11 bytes

Observe que a pilha foi retornada ao ponto onde foi salva, ou seja, na chamada da função setjmp. Nenhum dos loops chega jamais a completar uma única interação.

Existe uso para o long jump? Com certeza que sim, porém, da mesma forma como o goto, trata-se de um uso muito específico. O programador tem de ser muito criterioso no momento de usar estes recursos.

Página anterior     Próxima página

Páginas do artigo
   1. A polêmica
   2. O famigerado goto
   3. Longjump: O goto não local
   4. Variáveis globais: o código não aproveitável
   5. Conclusão
Outros artigos deste autor

Programando com uma granada de mão: uma visão da linguagem C

Como funcionam os alocadores de memória do STD C?

Leitura recomendada

Detectando assalto na multidão com visão computacional

Substituindo a biblioteca conio.h no Linux usando ncurses curses.h

C - Include e Makefile

Criando programas com suporte a arquivos de configuração com a libConfuse

Reprodução de arquivos WAV com SDL_mixer e linguagem C

  
Comentários
[1] Comentário enviado por removido em 21/06/2004 - 08:58h

Muito interessante o artigo! Nas universidades (pelo menos na minha) os professores de linguagem de programação ensinam como evitar estes "desastres"... eu acho isso muito válido! Já que os programadores de hoje, mais do que nunca, precisam levar em consideração a segurança da informação que seu programa vai manipular, tendo que procurar evitar ao máximo qualquer possibilidade de falha.

[2] Comentário enviado por jllucca em 21/06/2004 - 16:39h

Muito legal o artigo!

Na verdade não esperava menos de ti! :D

Continue assim :))

[]'s

[3] Comentário enviado por engos em 23/06/2004 - 16:12h

Achei a visão um pouco limitada nos exemplos, todavia o artigo está muito bom.

É sempre bom divulgar esse tipo de conhecimento, pois a maioria das pessoas que sabem que não devem usar, não possuem a menor idéia da razão de não usar.

Artigo muito interessante e útil, parabéns.

[4] Comentário enviado por ice em 11/10/2004 - 17:34h

Alguem ja ouviu falar do ((void(*)())0)()??

[5] Comentário enviado por ron_lima em 11/10/2004 - 20:10h

A declaração é complicada. Entendi que é um casting que dereferencia um pointer para função void. Está correto?

[6] Comentário enviado por ron_lima em 12/10/2004 - 06:31h

Complementando... Esta declaração é uma tentativa de ativação de código no endereço zero, ou seja, antes da função main nos sistemas operacionais modernos. O efeito é imediato: falha de segmentação.

[7] Comentário enviado por voulogarso1vez em 02/03/2005 - 22:43h

Faltou dizer que o setjmp/longjmp pode ser usado pra simular exceções (try/catch), apesar de difícil de fazer de forma segura (sem vazamento de memória, por exemplo). Quanto aos signals, junto com o longjump, eles são um outro canto escuro da linguagem... volatile, sig_atomic_t, sempre fico na dúvida sobre o que realmente é necessário pra deixar o código que usa esses recursos realmente portável e de acordo com os padrões.

[8] Comentário enviado por ron_lima em 06/03/2005 - 07:43h

A idéia inicial do setjmp e do longjmp não é simular o tratamento de exceções mas é realizar o que é chamado de "goto não local". Estas funções fazem um rewind da pilha, fazendo qualquer empilhamento que tenha ocorrido no programa retornar a uma posição salva anteriormente. É uma ferramenta poderosa e, no entanto, extremamente perigosa, pois pode ocasionar falhas como vazamentos de memória difíceis de serem encontradas. Os sinais são uma coisa muito interessante. Eles residem na área da programação assíncrona. Nunca se sabe o que o programa vai estar fazendo quando receber um sinal. Normalmente é interessante, em sistemas unix, programar um aplicativo para tratar de forma adequada determinado sinal para que o mesmo finalize o que está fazendo sem problemas. Normalmente programa-se handles para os sinais SIGHUP para reinicialização e SIGTERM para finalização.

[9] Comentário enviado por elgio em 31/08/2007 - 13:28h

Muito bom este tema.

Só uma ressalva: tua função alternatica para strcpy tem um bug: não copia o 0 final. Que tal esta:

char * strcpy (char * dest, const char * orig)
{
const char *o=orig;
char *d=dest;

while (*d++ = *o++);
return (dest);
}

[10] Comentário enviado por ron_lima em 31/08/2007 - 13:36h

De fato. Eis a versão corrigida:


char * strcpy (char * dest, const char * orig) {

const char * o;
char * d;

for (o=orig, d=dest; *o != '\ 0' ; ++o, ++d) {

*d = *o;

}
*d = 0; /* Entenda-se aqui o barra zero. Aparentemente há um problema no site. */
return dest;
}

A sua versão funciona adequadamente também (faltou um ; depois do return) apesar de ser menos clara. Seria possível criar-se uma versão ainda mais reduzida, partindo-se da sua:

char * strcpy (char * dest, const char * orig) {
char *d=dest;

while (*d++ = *orig++)
;
return (dest);
}

Considerando-se que os otimizadores atuais deixam o código com a mesma performance, eu optaria por manter a versão mais clara.

[11] Comentário enviado por elgio em 31/08/2007 - 13:38h

Oi. Já percebi que o VOL estraga alguns caracteres. o CONTRA-BARRA ZERO por exemplo...

Chato isto...

tente por apenas *d = 0

(se funcionar)

[12] Comentário enviado por elgio em 31/08/2007 - 13:42h

Correto. Minha versão é menos clara e de propósito: Envio ela para meus alunos como desafio do que ela faz, depois que mostrei ponteiros, evidentemente.

Até lá, o que eles conhecem de strcpy é isto :-(

int strcpy (char d[], char o[])
{
int i;
for (i = 0; o[i] != 0; i++){
d[i] = o[i];
}

d[i] = 0;

return (i); /* no caso retorna o numero de cars copiados (tamanho) */
}

[13] Comentário enviado por elgio em 31/08/2007 - 13:50h

Aproveitando o assunto (porque eu adoro C e apesar do teu artigo ser ANTIGO, eu só o descobri hoj. hehehehe).

Outro fato no C que PODE VIR A SER UM DESASTRE, se não feito com cuidado, é a manipulação de números de ponto flutuante. Nem estou falando da velha pegadinha:

int x=10;
double z;

z = x/4;

/* z infelizmente vai ter 2 e não 2.5 */

Mas eu envio também como desafio aos alunos (com a intenção de mostrar os perigos) este pra lá de polêmico trecho de código:

int a;

a = 200;
a = (a * 0.7) + a;

Isto deveria dar 340, pois 200 * 0.7 da 140 e não haveria perda de casas decimais, certo? Pois dá 339!!

Cara, acho este comportamento o mais interessante que já vi e, é claro, a explicação é perfeitmante lógica!!

[14] Comentário enviado por elgio em 31/08/2007 - 13:53h

Só para finalizar:
http://www.inf.ufrgs.br/~elgios/linux/programacao_C/problema1.html

[]'s
PS: teu artigo já entrou no meu favoritos

[15] Comentário enviado por ron_lima em 31/08/2007 - 14:25h

Sim, pelo fato de a * 0.7 resultar em 139.999999999997, efeito dos números em ponto flutuante.

Uma variação bacana do seu strcpy que determina quantos caracteres foram copiados poderia ser:

int strcpy (char *a, const char *b) {
const char *c = b;
while (*a++ = *c++)
;
return (c - b - 1);
}

Esta versão é também interessante pela adição da aritmética de ponteiros ao final. Mas ainda acho a função abaixo uma das mais interessantes, apesar de não aconselhar o uso de construções deste tipo:

void func (int *a, int *b) {
*a ^= *b;
*b ^= *a;
*a ^= *b;
}

[16] Comentário enviado por elgio em 31/08/2007 - 14:33h

Adorei.

Um swapp usando XOR.

Ta entrando na minha lista de desafios para os alunos.
Já que iniciamos um odisséia de "troca de figurinhas":

int main()
{

a=22;
a= a++ + a++;

a=22;
a= ++a + ++a;

a=22;
a= a++ + ++a;

a=22;
a= ++a + a++;

}

Mas este é o último :-D
Trocamos por email, senão vou queimar meus desafios todos aqui!!
[]'s

[17] Comentário enviado por f_Candido em 13/10/2007 - 23:32h

Bem exposto o conteúdo. Ficou muito Bom.
Parabéns.

[18] Comentário enviado por Chrys em 27/01/2008 - 15:52h

Contudo sobre a parte de static e variável global, neste caso nem exerce realmente a função de variável global, já que o tipo static definirá que essa variável só poderá ser vista dentro deste arquivo, em nenhum outro arquivo poderá acessar essa variável diretamente, o que é um procedimento seguro, tornando a variável global apenas no arquivo em questão e sendo até recomendado esse uso de getter.

Então se nesse caso você tiver uma variável static dentro do arquivo file.c e quiser acessa-la através do arquivo tentando.c, pode esquecer, pois diretamente fica "impossível" acessar, surgindo os famosos gets e sets da vida, acho que é a parti daí que sai o procedimento de segurança de código.

Uma dúvida é possível se fazer funções statics em C?

[19] Comentário enviado por ron_lima em 29/01/2008 - 08:44h

A variável declarada como estática (static) é sempre global, mesmo que não seja possível acessá-la de outra unidade de tradução. Mesmo quando declarada dentro de uma função, ela será alocada dentro da pilha global do programa.

Quando declarada fora de blocos, ela é global na unidade de tradução na qual foi declarada. Portanto, ela continua sendo uma variável global.

Da mesma forma, é possível declarar-se funções com o mesmo especificador de armazenamento. As funções "static" também são visíveis apenas dentro da unidade de tradução onde são declaradas e definidas.

Variável global é receita certa para programas altamente acoplados e de difícil manutenção, mesmo que o acesso seja controlado por "getters" e "setters". O ideal é não usá-las. Sempre é possível fornecer os dados necessários através de parâmetros de função.

Contudo, há casos em que é necessário o uso de variáveis globais. Por exemplo, no caso de eventos assíncronos que ocorram no seu programa - por exemplo, um sinal - que precisam ser tratados e ter seu estado controlado. Normalmente os handlers de eventos assíncronos são funções que não têm contato com o restante do programa - um handle de sinal é uma função void que recebe como parâmetro o número identificador do sinal que ocorreu. Para este caso, é indicado a variável global para que o estado do programa seja conhecido depois da ocorrência do evento assíncrono.


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts