C - uso do return em função void

Programação C em geral

Moderadores: 51, guest2003

Mensagempor barboza » 14 Abr 2010 19:32

tcpipchip escreveu:
void calcula(unsigned char v1,v2)
{
return(v1+v2);
}




Não entendi o proposito do exemplo, mas isso ai nem compila.
Os homens mentiriam muito menos se as mulheres fizessem menos perguntas.
Avatar do usuário
barboza
Word
 
Mensagens: 948
Registrado em: 17 Out 2006 13:42
Localização: Longe de onde gostaria de estar

Mensagempor msamsoniuk » 14 Abr 2010 22:36

e vc esta corretissimo tcpipchip! :)

a ocorrencia de underflow acho que depende da implementacao do compilador... no caso do gcc para 68k, realmente nao apenas nao ocorre, como tambem existem instrucoes do processador que conseguem desfazer qq salada que ocorra dentro da funcao chamada... eu verifiquei isso fazendo o codigo correto:

Código: Selecionar todos
#include <stdio.h>

int main()
{
  int v;
  v = calcula(2,3);
 
  printf("%d\n",v);
}

void calcula(int v1, int v2)
{
  int r;
  r = v1+v2;
  return r;
}


gerando o assembler e analisando passo a passo:

Código: Selecionar todos
main:
        link.w %a6,#-4 // reserva espaco para v, stack contem "a6 v"
        pea 3.w // passa valor 3 para stack
        pea 2.w // passa valor 2 para stack
        jbsr calcula // chama funcao, veja ali embaixo! stack contem "a6 v 3 2 ret"
        addq.l #8,%sp // joga fora os caras anteriores, stack contem "a6 v"
        move.l %d0,-4(%a6) // salva em v valor retornado via d0
        move.l -4(%a6),-(%sp) // empilha v, stack contem  "a6 v v"
        pea .LC0 // empilha string, stack contem "a6 v v *p"
        jbsr printf // chama printf
        addq.l #8,%sp // retorna stack ao que era: "a6 v", retorno de printf em d0 descartado.
        move.l -4(%a6),%d0 // coloca retorno em d0
        unlk %a6 // demonta estrutura de a6 e v, stack vazia
        rts // retorna
calcula:
        link.w %a6,#-4 // reserva espaco para r, stack contem "3 2 ret a6 r"
        move.l 8(%a6),%d0 // puxa primeira variavel da stack
        add.l 12(%a6),%d0 // puxa segunda variavel e soma
        move.l %d0,-4(%a6) // salva soma em r
        move.l -4(%a6),%d0 // retorna a funcao em d0 ???
        unlk %a6 // desmonta reserva, ou seja, stack contem "3 2 ret"
        rts // retorna, ou seja stack contem "3 2"


fica claro q ele previne bem... quando chama uma funcao com jbsr ele empilha o pc, q eh desempilhado com rts no fim da funcao. quando entra na funcao, ele aloca espaco com link, mas desaloca automaticamente com unlk (fala serio, o conforto de usar um processador CISC nao tem preco! hehehe).

e no caso dos parametros, q ele passa com pea, ele remove depois ajustando o sp exatamente com o valor dos parametros inseridos. fechando com tudo isso, o retorno ocorre em um registro, o q garante q nao tenha underflow.

bom, a magica mais interessante eh a instrucao link: ela salva o registro a6 na stack atual e copia o sp para a6... a partir disso, vc pode zuar a vontade o sp! inclusive perder o valor dela! no fim da funcao, ele se recupera copiando a6 para sp e assim recuperando a stack original, para entao recuperar o a6 original e retornar corretamente.

colocar void com esse compilador em especial mostrou-se um problema: ele otimiza o codigo, verifica q nao tem o q retornar e suprime a funcao inteira! hehehe mas no gcc para x86 ele gera codigo... uma salada de codigo comparado ao 68k, mas com alguma dor e sofrimento eh possivel entender alguma coisa:

Código: Selecionar todos
        pushl   %ebp // salva ebp na stack
        movl    %esp, %ebp // copia esp para ebp
        pushl   %ebx // salva ebx
        subl    $36, %esp // aloca espaco para 36 bytes?!?
        call    L3 // faz um call para ali embaixo
"L00000000001$pb": // soh deus sabe!!!
L3:
        popl    %ebx // desempilha ebx?!? pq? pq? pq? lah embaixo tb de novo?!? pq?
        movl    $3, 4(%esp) // salva valor 3 na stack
        movl    $2, (%esp) // salva valor 2 na stack
        call    L_calcula$stub // chama funcao calcula, acompanhe ali embaixo!
        movl    %eax, -12(%ebp) // deve ter retornado em eax... apesar de ser void?!? naoooooooooooooooo!!!
        movl    -12(%ebp), %eax // ele grava, retorna, eh uma salada...
        movl    %eax, 4(%esp) // empilha eax, finalmente
        leal    LC0-"L00000000001$pb"(%ebx), %eax // calcula o endereco da string...hmmmm
        movl    %eax, (%esp) // empilha o endereco da string, finalmente!!!
        call    L_printf$stub // chama printf
        movl    -12(%ebp), %eax // desaloca a salada acima
        addl    $36, %esp // desaloca os tais 36 bytes...
        popl    %ebx // recupera ebx... que ebx?!? nao saiu lah em cima jah?!?
        leave // em teoria, similar a unlk do 68k
        ret // retorna, finalmente! jah nao aguentava mais isso!
_calcula:
        pushl   %ebp // guarda ebp
        movl    %esp, %ebp // guarda esp em ebp
        subl    $24, %esp // aloca espaco para 24 bytes?!? enfim...
        movl    12(%ebp), %eax // puxa o primeiro inteiro
        addl    8(%ebp), %eax // soma com o segundo inteiro
        movl    %eax, -12(%ebp) // salva eax na stack em algum lugar...
        leave // desmonta a salada
        ret // e retorna


mas eh uma salada tao grande q nem acompanhando passo a passo se entende alguma coisa... se ele tambem passa como parametro, o void lah eh completamente ignorado, pq eax retorna o calculo e ele imprime na boa o resultado hehehe
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Mensagempor barboza » 14 Abr 2010 23:34

O Keil gera outro erro de compilação com este codigo exemplo!

Código: Selecionar todos
#include <stdio.h>

void calcula(int v1, int v2)
{
  int r;
  r = v1+v2;
  return r;
}

int main()
{
  int v;
  v = calcula(2,3);
 
  printf("%d\n",v);
}



Para 8051:

compiling asm51.c...
ASM51.C(7): error C174: return-expression on void-function
Target not created

Para ARM:

compiling asm51_arm.c...
asm51_arm.c(7): error: #118: a void function may not return a value
asm51_arm.c(13): error: #513: a value of type "void" cannot be assigned to an entity of type "int"
Target not created
Os homens mentiriam muito menos se as mulheres fizessem menos perguntas.
Avatar do usuário
barboza
Word
 
Mensagens: 948
Registrado em: 17 Out 2006 13:42
Localização: Longe de onde gostaria de estar

Mensagempor msamsoniuk » 15 Abr 2010 02:33

eh realmente bem estranho... no gcc x86 ele compila e, pior, *roda*:

Código: Selecionar todos
$ gcc asm51/truta.c
asm51/truta.c:14: warning: conflicting types for ‘calcula’
asm51/truta.c:6: warning: previous implicit declaration of ‘calcula’ was here
asm51/truta.c: In function ‘calcula’:
asm51/truta.c:19: warning: ‘return’ with a value, in function returning void
$ ./a.out
5


mas para o gcc 68k ele nao consegue finalizar a compilacao do mesmo codigo, pois o objeto calcula nao existe. uma alternativa seria separar em arquivos diferentes. no main vc declara o prototipo da funcao como int calcula(int, int):

Código: Selecionar todos
extern int calcula(int, int);

int main()
{
  int v;
  v = calcula(2,3);
     
  printf("%d\n",v);
}



mas no outro arquivo que contem a funcao vc declara com void e faz outra coisa com os parametros:

Código: Selecionar todos
void calcula(int v1, int v2)
{
  int r;
  r = v1+v2;
}


dae ele compila de boa separadamente:

Código: Selecionar todos
$ m68k-elf-gcc -c a.c
$ m68k-elf-gcc -c b.c
$ m68k-elf-ld a.o b.o
m68k-elf-ld: warning: cannot find entry symbol _start; defaulting to 80000074
$ file a.out
a.out: ELF 32-bit MSB executable, Motorola 68020, version 1 (SYSV), statically linked, not stripped


mas olhando o assembler de cada um separadamente, que piada, a soma r = v1+v2 eh feita em d0, dae por coincidencia o lixo resultando acaba sendo usado no printf, pq ele esperava o resultado em d0, daih acaba funcionando do mesmo jeito! hehehe :)
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Mensagempor Jozias del Rios » 05 Nov 2010 16:41

Marcelo Samsoniuk escreveu:a ocorrencia de underflow acho que depende da implementacao do compilador... no caso do gcc para 68k, realmente nao apenas nao ocorre, como tambem existem instrucoes do processador que conseguem desfazer qq salada que ocorra dentro da funcao chamada... eu verifiquei isso fazendo o codigo correto:

Código: Selecionar todos
#include <stdio.h>
int main()
{
  int v;
  v = calcula(2,3);
  printf("%d\n",v);
}

void calcula(int v1, int v2)
{
  int r;
  r = v1+v2;
  return r;
}


gerando o assembler e analisando passo a passo:

colocar void com esse compilador em especial mostrou-se um problema: ele otimiza o codigo, verifica q nao tem o q retornar e suprime a funcao inteira! hehehe mas no gcc para x86 ele gera codigo... uma salada de codigo comparado ao 68k, mas com alguma dor e sofrimento eh possivel entender alguma coisa:

Código: Selecionar todos
        pushl   %ebp // salva ebp na stack
        movl    %esp, %ebp // copia esp para ebp
        pushl   %ebx // salva ebx
        subl    $36, %esp // aloca espaco para 36 bytes?!?
        call    L3 // faz um call para ali embaixo
"L00000000001$pb": // soh deus sabe!!!
L3:
        popl    %ebx // desempilha ebx?!? pq? pq? pq? lah embaixo tb de novo?!? pq?
        movl    $3, 4(%esp) // salva valor 3 na stack
        movl    $2, (%esp) // salva valor 2 na stack
        call    L_calcula$stub // chama funcao calcula, acompanhe ali embaixo!
        movl    %eax, -12(%ebp) // deve ter retornado em eax... apesar de ser void?!? naoooooooooooooooo!!!
        movl    -12(%ebp), %eax // ele grava, retorna, eh uma salada...
        movl    %eax, 4(%esp) // empilha eax, finalmente
        leal    LC0-"L00000000001$pb"(%ebx), %eax // calcula o endereco da string...hmmmm
        movl    %eax, (%esp) // empilha o endereco da string, finalmente!!!
        call    L_printf$stub // chama printf
        movl    -12(%ebp), %eax // desaloca a salada acima
        addl    $36, %esp // desaloca os tais 36 bytes...
        popl    %ebx // recupera ebx... que ebx?!? nao saiu lah em cima jah?!?
        leave // em teoria, similar a unlk do 68k
        ret // retorna, finalmente! jah nao aguentava mais isso!
_calcula:
        pushl   %ebp // guarda ebp
        movl    %esp, %ebp // guarda esp em ebp
        subl    $24, %esp // aloca espaco para 24 bytes?!? enfim...
        movl    12(%ebp), %eax // puxa o primeiro inteiro
        addl    8(%ebp), %eax // soma com o segundo inteiro
        movl    %eax, -12(%ebp) // salva eax na stack em algum lugar...
        leave // desmonta a salada
        ret // e retorna


mas eh uma salada tao grande q nem acompanhando passo a passo se entende alguma coisa... se ele tambem passa como parametro, o void lah eh completamente ignorado, pq eax retorna o calculo e ele imprime na boa o resultado hehehe


MS, em passos, o que ele faz é:

push ebp / move esp, ebp prepara a pilha da função
push ebx / sub esp, 36 / call L3 / pop ebx salva ebx, pois o seu valor não poderia ser alterado pela função (calling conventions) e coloca em ebx o ponteiro para o label L3 em ebx.
mov [esp+4], 3 / mov [esp], 2 realmente escreve os valores 2 e 3 no espaço de variáveis locais da stack. Agora temos 36-8 = 28 bytes ainda disponíveis.
call L_calcula chama a função. Ok. Veja a seguir
mov [ebp-12], eax obteve o valor de retorno, que pelo jeito não foi ditado pela futura declaração da função, mas sim pela tentativa de advinhar o seu protótipo que não havia sido definido previamente! o valor de retorno foi salvo em um espaço local da função.
mov eax, [ebp-12] / mov [esp+4], eax copia o valor retornado da função para [esp+4], preparando-se para chamar printf. Percebe-se que o código não está otimizado, mas está complilado para facilitar a depuração.
lea eax, [ebx + LC0-L3] de fato coloca em eax o ponteiro para a string, de forma de código relocável, não absoluto, por isso precisou capturar o valor do instruction pointer no inicio!
mov [esp], eax / call printf salva o ponteiro para a string na pilha e chama a função printf...
mov eax, [ebp-12] obtem o valor de retorno de printf, embora não use.
add esp, 36 desaloca os 36 bytes reservados na pilha para armazenamento local. Destes 36 bytes, ele só usou 12 bytes.
pop ebx restaura o valor de ebx que não deveria ser alterado pela função main
leave / ret faz mov esp, ebp e em seguida pop bp, e então retorna de main.

_calcula:
push ebp / mov ebp, esp / sub esp, 24 inicia o stack frame local com 24 bytes locais... parece estar sempre superdimensionando o consumo da pilha.
mov eax, [ebp+12] / add eax, [ebp+8] / mov [ebp-12], eax faz a conta de fato, sem usar o frame local.
mov [ebp-12], eax salva o resultado no espaço local da pilha, embora não use.
leave / ret retorna

O que se pode inferir é: primeiro que aquilo que "só deus sabe" é, na verdade, um "alias label" de L3.

Na sua análise, esqueceu que "call L3 / pop ebx" não deixa resíduo no stack, assim não houve stck underflow por excesso de "pop ebx".

Esse compilador arcaico e essa sintaxe AT&T são muito chatas de serem analisadas...

O compilador está sempre reservando 24 bytes da pilha de todas as funções para alguma finalidade não revelada...
Os vencedores são aqueles que sabem o que fazer se perderem.
Os perdedores são aqueles que não sabem o que fazer se ganharem.
Avatar do usuário
Jozias del Rios
Byte
 
Mensagens: 279
Registrado em: 31 Out 2009 03:36
Localização: SJCampos-SP

Anterior

Voltar para Visual C++/C/C++/C#

Quem está online

Usuários navegando neste fórum: Nenhum usuário registrado e 1 visitante

x