Você usa goto nos seus códigos em C?

- por Sergio Prado

Categorias: Linguagem C Tags: , ,

Esses dias estava trabalhando em um device driver para Linux de um display LCD de 3.5", e me deparei com o código abaixo, responsável pela inicialização do display:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
static int __init s3c24xxfb_probe(struct platform_device *pdev,
                                  enum s3c_drv_type drv_type)
{
    struct s3c2410fb_info *info;
    struct s3c2410fb_display *display;
    struct fb_info *fbinfo;
    struct s3c2410fb_mach_info *mach_info;
    struct resource *res;
    int ret;
    int irq;
    int i;
    int size;
    u32 lcdcon1;
 
    mach_info = pdev->dev.platform_data;
    if (mach_info == NULL) {
        dev_err(&pdev->dev,
                "no platform data for lcd, cannot attach\n");
        return -EINVAL;
    }
 
    if (mach_info->default_display >= mach_info->num_displays) {
        dev_err(&pdev->dev, "default is %d but only %d displays\n",
                mach_info->default_display, mach_info->num_displays);
        return -EINVAL;
    }
 
    display = mach_info->displays + mach_info->default_display;
 
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "no irq for device\n");
        return -ENOENT;
    }
 
    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
    if (!fbinfo)
        return -ENOMEM;
 
    platform_set_drvdata(pdev, fbinfo);
 
    info = fbinfo->par;
    info->dev = &pdev->dev;
    info->drv_type = drv_type;
 
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (res == NULL) {
        dev_err(&pdev->dev, "failed to get memory registers\n");
        ret = -ENXIO;
        goto dealloc_fb;
    }
 
    size = (res->end - res->start) + 1;
    info->mem = request_mem_region(res->start, size, pdev->name);
    if (info->mem == NULL) {
        dev_err(&pdev->dev, "failed to get memory region\n");
        ret = -ENOENT;
        goto dealloc_fb;
    }
 
    info->io = ioremap(res->start, size);
    if (info->io == NULL) {
        dev_err(&pdev->dev, "ioremap() of registers failed\n");
        ret = -ENXIO;
        goto release_mem;
    }
 
    info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
 
    dprintk("devinit\n");
 
    strcpy(fbinfo->fix.id, driver_name);
 
    /* Stop the video */
    lcdcon1 = readl(info->io + S3C2410_LCDCON1);
    writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
 
    fbinfo->fix.type      = FB_TYPE_PACKED_PIXELS;
    fbinfo->fix.type_aux  = 0;
    fbinfo->fix.xpanstep  = 0;
    fbinfo->fix.ypanstep  = 0;
    fbinfo->fix.ywrapstep = 0;
    fbinfo->fix.accel     = FB_ACCEL_NONE;
 
    fbinfo->var.nonstd      = 0;
    fbinfo->var.activate    = FB_ACTIVATE_NOW;
    fbinfo->var.accel_flags = 0;
    fbinfo->var.vmode       = FB_VMODE_NONINTERLACED;
 
    fbinfo->fbops           = &s3c2410fb_ops;
    fbinfo->flags           = FBINFO_FLAG_DEFAULT;
    fbinfo->pseudo_palette  = &info->pseudo_pal;
 
    for (i = 0; i < 256; i++)
        info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
 
    ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
    if (ret) {
        dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
        ret = -EBUSY;
        goto release_regs;
    }
 
    info->clk = clk_get(NULL, "lcd");
    if (!info->clk || IS_ERR(info->clk)) {
        printk(KERN_ERR "failed to get lcd clock source\n");
        ret = -ENOENT;
        goto release_irq;
    }
 
    clk_enable(info->clk);
    dprintk("got and enabled clock\n");
 
    msleep(1);
 
    info->clk_rate = clk_get_rate(info->clk);
 
    /* find maximum required memory size for display */
    for (i = 0; i < mach_info->num_displays; i++) {
        unsigned long smem_len = mach_info->displays[i].xres;
 
        smem_len *= mach_info->displays[i].yres;
        smem_len *= mach_info->displays[i].bpp;
        smem_len >>= 3;
        if (fbinfo->fix.smem_len < smem_len)
            fbinfo->fix.smem_len = smem_len;
    }
 
    /* Initialize video memory */
    ret = s3c2410fb_map_video_memory(fbinfo);
    if (ret) {
        printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
        ret = -ENOMEM;
        goto release_clock;
    }
 
    dprintk("got video memory\n");
 
    fbinfo->var.xres = display->xres;
    fbinfo->var.yres = display->yres;
    fbinfo->var.bits_per_pixel = display->bpp;
 
    s3c2410fb_init_registers(fbinfo);
 
    s3c2410fb_check_var(&fbinfo->var, fbinfo);
 
    ret = s3c2410fb_cpufreq_register(info);
    if (ret < 0) {
        dev_err(&pdev->dev, "Failed to register cpufreq\n");
        goto free_video_memory;
    }
 
    ret = register_framebuffer(fbinfo);
    if (ret < 0) {
        printk(KERN_ERR "Failed to register framebuffer device: %d\n",
               ret);
        goto free_cpufreq;
    }
 
    /* create device files */
    ret = device_create_file(&pdev->dev, &dev_attr_debug);
    if (ret) {
        printk(KERN_ERR "failed to add debug attribute\n");
    }
 
    printk(KERN_INFO "fb%d: %s frame buffer device\n",
           fbinfo->node, fbinfo->fix.id);
 
    return 0;
 
free_cpufreq:
    s3c2410fb_cpufreq_deregister(info);
free_video_memory:
    s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
    clk_disable(info->clk);
    clk_put(info->clk);
release_irq:
    free_irq(irq, info);
release_regs:
    iounmap(info->io);
release_mem:
    release_resource(info->mem);
    kfree(info->mem);
dealloc_fb:
    platform_set_drvdata(pdev, NULL);
    framebuffer_release(fbinfo);
    return ret;
}

Esqueça um pouco o que a função faz, e perceba como ela implementa o tratamento de erros. Para cada erro identificado, ela usa chamadas a goto para tratar o erro e fazer uma limpeza antes de retornar da função (desalocar buffers previamente alocados, liberar recursos, configurar portas de I/O, etc). São ao todo oito chamadas a goto apenas nesta função!

Não aprendemos que usar goto é uma técnica ruim? Também não aprendemos que todo código bem estruturado em C não deve ter nenhuma chamada a goto? Então como é possível encontrar um código assim dentro do kernel do Linux?

NÃO SE ASSUSTE

Usar goto em código C pode ser mais comum do que você imagina. Fiz um levantamento do uso da palavra-chave "goto" no kernel do Linux 2.6.37, no Busybox e no U-Boot.

Apenas o core do kernel (/kernel), possui 1.417 usos de goto. Quando incluimos os drivers de dispositivo (/drivers) e o código dependente de arquitetura (/arch) são 54.223 usos de goto. Fazendo a contagem completa, chegamos ao impressionante numero de 84.719 usos de goto no kernel do Linux!

Mas não é só o kernel do Linux que abusa de goto. O pacote Busybox, comum em sistemas embarcados com Linux, usa o goto 1.782 vezes. E o famoso bootloader U-Boot utiliza em 1.662 oportunidades.

Ok Sergio, então você esta me dizendo que estou livre para usar e abusar de goto no meu código?

Calma aí, não é bem assim!

BAIXANDO O NÍVEL

Veja o código que o compilador gcc gera em uma chamada a goto, comparando o fonte em C e o arquivo assembly gerado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
    int i = 1;
 
    if (i) {
        printf("Goto error!\n");
        goto error;
    }
 
    printf("OK!\n");
 
    return 0;
 
error:
    return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    .file   "goto.c"
    .section    .rodata
.LC0:
    .string "Goto error!"
.LC1:
    .string "OK!"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $1, 28(%esp)
    cmpl    $0, 28(%esp)
    je  .L2
    movl    $.LC0, (%esp)
    call    puts
    nop
.L3:
    movl    $1, %eax
    jmp .L4
.L2:
    movl    $.LC1, (%esp)
    call    puts
    movl    $0, %eax
.L4:
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

Veja que o if na linha 5 do fonte C gera uma chamada a "je .L2" (jump condicional) na linha 17 do código Assembly. E o goto na linha 7 do fonte C gera uma chamada a "jmp .L4" (jump) na linha 23 do código assembly.

Perceba que, na prática, tudo é tratado com uma instrução de salto no nível do assembly, não importa se você esta usando goto, if/else, switch, do/while ou for. Não existe nenhum impacto na performance ou no uso de memória da aplicação. Na verdade, em alguns casos, o uso de goto pode até melhorar a performance da aplicação. Mas normalmente seu uso é uma questão de legibilidade do código.

Os mais puristas afirmam que não existe nada que se escreva usando goto, que não possa ser escrito usando as estruturas de controle disponíveis na linguagem C (if/else, switch, do/while, for), e durante um bom tempo também acreditei nisso. Mas na prática, às vezes, a teoria é diferente! E existem alguns casos onde o uso de goto pode ser a melhor solução.

Sergio, você já não falou que é contra o uso de goto em código C? Sim, mas isso não muda o fato de que pode existir uma forma de utilizar esta palavra-chave melhorando a estrutura e a legibilidade do seu programa.

Mas você também não esta sempre defendendo qualidade de código, segurança, bla bla bla, e agora vem me falar que existe uma utilidade para o uso de "goto"? Sim, existe! É só olhar para os números (e para o código-fonte do kernel do Linux, e para outros tantos códigos open-source). Essa utilidade chama-se: Tratamento de Erros!

O PROBLEMA EM QUESTÃO

Uma das deficiências da linguagem C é a ausência de um mecanismo simples e eficiente de tratamento de erros. Já escrevi sobre isso neste post aqui.

O problema fica mais transparente quando precisamos lidar com limpeza (desalocação) de recursos. Dê uma olhada no exemplo abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int initRTC()
{
    char *buf1, *buf2;
 
    if ((buf1 = (char *)malloc(20)) == NULL)
        return ERR_RTC_MALLOC;
 
    if ((buf2 = (char *)malloc(40)) == NULL) {
        free(buf1);
        return ERR_RTC_MALLOC;
    }
 
    if (openRTC()) {
        free(buf2);
        free(buf1);
        return ERR_RTC_OPEN;
    }
 
    if (cfgRTC(buf1, 20, buf2, 40)) {
        closeRTC();
        free(buf2);
        free(buf1);
        return ERR_RTC_CFG;
    }
 
    if (txCmd(buf1, 20, buf2, 40)) {
        closeRTC();
        free(buf2);
        free(buf1);
        return ERR_RTC_TX;
    }
 
    closeRTC();
    free(buf2);
    free(buf1);
 
    return RTC_OK;
}

Esta função faz duas alocações de memória, abre a comunicação com o RTC, configura os buffers e transmite. Perceba que existe vários pontos de retorno.

Na linha 8, em caso de erro na chamada a malloc() para o buf2, é necessário liberar a memória alocada para buf1 antes de retornar. Da mesma forma, se der erro ao abrir a comunicação com o RTC na linha 13, é necessário liberar a memória alocada em buf1 e buf2. E por aí vai.

Para cada ponto de retorno adicional, precisamos lembrar de fazer a limpeza necessária antes de retornar. Veja que os trechos de código responsáveis pela limpeza são replicados nos vários pontos de retorno. Imagine o problema e os riscos envolvidos em dar manutenção num código desse tipo.

Veja agora a mesma função reescrita usando goto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int initRTC()
{
    char *buf1, *buf2;
    int ret = RTC_OK;
 
    if ((buf1 = (char *)malloc(20)) == NULL) {
        ret = ERR_RTC_MALLOC;
        goto fim0;
    }
 
    if ((buf2 = (char *)malloc(40)) == NULL) {
        ret = ERR_RTC_MALLOC;
        goto fim1;
    }
 
    if (openRTC()) {
        ret = ERR_RTC_OPEN;
        goto fim2;
    }
 
    if (cfgRTC(buf1, 20, buf2, 40)) {
        ret = ERR_RTC_CFG;
        goto fim3;
    }
 
    if (txCmd(buf1, 20, buf2, 40)) {
        ret = ERR_RTC_TX;
        goto fim3;
    }
 
fim3:
    closeRTC();
fim2:
    free(buf2);
fim1:
    free(buf1);
fim0:
    return ret;
}

Agora a função tem apenas um ponto de retorno e não existe mais código replicado para fazer a limpeza (desalocação de recursos) em caso de erro, o que facilita a leitura e futuras manutenções. Me parece uma solução interessante para um problema comum e sem solução fácil em C.

É claro que existem outros mecanismos para resolver este mesmo problema. Você pode criar uma máquina de estados, conforme o exemplo abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
int initRTC()
{
    char *buf1, *buf2;
    int ret = RTC_OK, state = ST_BUF1;
 
    while (state != ST_DONE) {
 
        switch(state) {
 
            case ST_BUF1:
                if ((buf1 = (char *)malloc(20)) == NULL) {
                    ret = ERR_RTC_MALLOC;
                    state = ST_DONE;
                }
                else {
                    state = ST_BUF2;
                }
                break;
 
            case ST_BUF2:
                if ((buf2 = (char *)malloc(40)) == NULL) {
                    ret = ERR_RTC_MALLOC;
                    state = ST_ERR_BUF2;
                }
                else {
                    state = ST_OPEN_RTC;
                }
                break;
 
            case ST_OPEN_RTC:
                if (openRTC()) {
                    ret = ERR_RTC_OPEN;
                    state = ST_ERR_OPEN;
                }
                else {
                    state = ST_CFG_RTC;
                }
                break;
 
            // ....
 
            case ST_ERR_BUF2:
                free(buf1);
                state = ST_DONE;
                break;
 
            case ST_ERR_OPEN:
                free(buf2);
                state = ST_ERR_BUF2;
                break;
 
            case ST_ERR_TX:
                closeRTC();
                state = ST_ERR_OPEN;
                break;
 
            // ....
        }
    }
 
    return ret;
}

Não implementei todos os estados, mas dá para ter uma idéia do mecanismo utilizado. Perceba a complexidade deste código, comparado ao código com goto.

Podemos também aninhar chamadas if/else e controlar a desalocação de recursos com alguns flags:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int initRTC()
{
    char *buf1, *buf2;
    int ret = RTC_OK;
 
    if ((buf1 = (char *)malloc(20)) == NULL) {
        ret = ERR_RTC_MALLOC1;
    }
    else if ((buf2 = (char *)malloc(40)) == NULL) {
        ret = ERR_RTC_MALLOC2;
    }
    else if (openRTC()) {
        ret = ERR_RTC_OPEN;
    }
    else if (cfgRTC(buf1, 20, buf2, 40)) {
        ret = ERR_RTC_CFG;
    }
    else if (txCmd(buf1, 20, buf2, 40)) {
        ret = ERR_RTC_TX;
    }
 
    if (ret >= ERR_RTC_CFG) {
        closeRTC();
    }
 
    if (ret >= ERR_RTC_OPEN) {
        free(buf2);
    }
 
    if (ret >= ERR_RTC_MALLOC2) {
        free(buf1);
    }
 
    return ret;
}

Neste exemplo estamos usando a própria variável de retorno como flag para desalocação de recursos. O problema deste tipo de código é que nem sempre temos a possibilidade de aninhar if's e else's desta forma. Esta técnica pode também deixar o código muito sujo, dependendo da sua complexidade.

Ainda neste exemplo, você poderia usar o próprio estado do recurso para verificar se deve realizar a desalocação, implementando no fim da função:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    //...
 
    if (rtcOpenned) {
        closeRTC();
    }
 
    if (buf2 != NULL) {
        free(buf2);
    }
 
    if (buf1 != NULL) {
        free(buf1);
    }
 
    //...

Mas nem sempre você tem o controle de quais recursos precisa desalocar. De qualquer forma, o código com if/else aninhados ainda continua mais complicado de ler, quando comparado à solução com goto.

ONDE ESTA O PROBLEMA ENTÃO?

Se o código com goto neste caso é menos complicado de ler, e o binário gerado não sofre nenhum impacto na performance ou no uso de memória, porque não usá-lo?

Assim como grande parte dos problemas da vida, o erro esta no excesso. É muito fácil abusar deste recurso, e deixar o código cheio de "idas" e "vindas" (o famoso código-espaguete!).

Foi o que aconteceu no passado, e isso ajudou a criar a má reputação do uso de goto. Pense sempre nos prós e contras, reflita sobre sua forma de pensar e use os recursos disponíveis na linguagem com muita sabedoria.

Não foi meu objetivo neste artigo fazer qualquer tipo de apologia ao uso de "goto" em linguagem C, muito pelo contrário! Particularmente evito seu uso sempre que possível. Apenas acho válida uma análise mais profunda de nossos paradigmas de programação. Eu procuro sempre "guardar na manga" algumas técnicas para usar quando necessário.

E você? O que acha do uso de goto em linguagem C? Conhece alguma outra técnica para tratar o problema citado acima?

Um abraço,

Sergio Prado

  • Vitor Bicca

    Parabéns…
    Desenvolvo software há praticamente 30 anos…
    Atualmente utilizo o NETEXPRESS (Cobol da Microfocus para Windows) para aplicativos comerciais e aplicações industriais (chão de fábrica) inclusive. E desenvolvo alguma coisa para microcontroladores usando linguagem "C" e sistemas para CLP.
    Nunca tinha visto uma dissertação tão bem feita.
    A discriminação do GOTO (GO TO em COBOL) é feita há muitos anos… em prol da dita "Programação Estruturada"… Nunca concordei. Assim como a discriminação do COBOL, uma linguagem fácil, praticamente um inglês… com seus IF, ELSE, GOTO, PERFORM (call), MOVE, etc…
    A clareza do código não está apenas no uso de comandos (ou desuso)… já vi muito código, dito "estruturado", mais enrredado que novelo de lã em mão de criança.
    Gosto muito dos teus artigos, continue escrevendo e "esclarecendo" programadores.
    Mais uma vez… PARABÉNS pelo trabalho.

  • Parabéns pelo ótimo artigo. Muito claro e bem escrito.
    Não sei se foi por influência do assembly, por usar bastante a instrução jump, meus primeiros códigos em C para sistemas embarcados tinham muitos gotos. O que a primeira vista parece ser a salvação. Porém com o aumento do programa você percebe que o código parece mesmo com um espaguete e fica  difícil de entender o fluxo de execução.
     
    Um abraço.

     

  • A tendencia do programador novato é usar goto, ali tudo parece fazer mais sentido, o que certamente não deve ser verdade, dada sua bagagem.
    Já um programador experiente, consegue utilizar o goto a seu favor, de fato, existem códigos que ficam muito mais elegantes com goto, e até, poderiam não ter outra alternativa elegante sem ele.
    É possível entender entender muito sobre um programador, somente pela forma como ele utiliza o goto.
    Aplicar o goto de forma inteligente é uma arte.

  • Flavio B. Herrera

    Parabéns Sergio! Otimo artigo.
    Já programo a alguns anos em assembly, estou começando em C e não tem como, o jump vira goto mesmo como o Diego comentou. É bom para eu aprender a começar a usar o goto da melhor maneira possivel.
    Abraços

  • Excelente texto!
    Thread famosa sobre uso de "goto" no kernel do linux: http://kerneltrap.org/node/553/2131

    • Bem lembrado Kenzo!

      Tinha lido este post um tempo atrás, mas já tinha me esquecido.

      Um abraço!

  • Rafael

    Eu acho que, se disponível no SO, deve-se SEMPRE usar C++.
    ˜C˜ cai bem em algumas coisas, porém, são apenas ALGUMAS coisas (driver, kernel e que eu lembre no momento, só!!!), para todo o resto, DEVE-SE usar C++.
    Aprender uma linguagem completa e orientada a objetos não é tão difícil.

  • Franklin

    Este exemplo que você citou é o único em que é válido usar goto. Inclusive no manual do Linus Torvalds para programar o kernel do Linux ele fala isso: Executar limpeza antes de retornar um erro.
    É preciso estabelecer limites: Se o seu goto vai para uma linha acima da que chamou, com certeza ele não devia estar ali.
    Em C é possível fazer construções como: (proibidas em C++)
    void funcao(){
        goto abc;
        int x = 10;
    abc:
       ;
    }
    E o x não vai ser criado.
    No caso geral, goto é uma má ideia, principalmente porque as pessoas não sabem usar.

  • Eduardo

    Apesar de todos os argumentos a favor do goto, confesso que infelizmente (ou felizmente) não consigo me convencer que o uso do goto seja necessário em algum código. Após 11 anos programando em C, sempre consegui ter uma solução satisfatória sem o uso de goto, e sem perder em performance e otimização de código (trabalho com CPUs de 8kb).
    O goto quebra o conceito de linguagem estruturada, pois tem o poder de quebrar qualquer estrutura if, else, for, while, etc. Para mim, esta foi uma das grandes evoluções em relação ao assembly.
    Vejam como ficaria um dos códigos acima escrito sem goto:
    int initRTC()
    {
        char *buf1, *buf2;
        int ret;
        buf1 = 0;
        buf2 = 0;
     
        if ((buf1 = (char *)malloc(20)) != NULL)
        {
            if ((buf2 = (char *)malloc(40)) != NULL)
            {
                if (openRTC() != 0)
                {
                    if (cfgRTC(buf1, 20, buf2, 40) != 0)
                    {
                        if (txCmd(buf1, 20, buf2, 40) != 0)
                        {
                            ret = RTC_OK;
                        }
                        else
                        {
                            ret = ERR_RTC_TX;
                        }
                    }
                    else
                    {
                        ret = ERR_RTC_CFG;
                    }
                    closeRTC();
                }
                else
                {
                    ret = ERR_RTC_OPEN;
                }
            }
            else
            {
                ret = ERR_RTC_MALLOC;
            }
        }
        else
        {
            ret = ERR_RTC_MALLOC;
        }
     
        if( buf1 )
            free(buf1);
     
        if( buf2 )
            free(buf2);
        return ret;
    }
    O risco do goto não está na primeira construção da função, mas sim para quem vai dar manutenção e incluir novas coisas em uma função com goto. Facilmente um goto passa desapercebido em uma inspeção de código.
    O problema não é tão grande quando os saltos são sempre para frente. Mas quando faz saltos para trás, aí é que mora o problema.
    Essa é só uma opinião, é claro que cada um deve ter a sua.
    Independente disto, ótimo artigo Sergio, parabéns!

    • Olá Eduardo!

      Ótimo argumento. Valeu pelas dicas!

      Um abraço!

  • Lucas Pina

    Parabéns por mais um ótimo artigo!
     
    Também evito ao máximo o uso de goto, a não ser em casos onde quero sair de um loop dentro de outros loops (coisa também que sempre evito). Para este caso específico, provavelmente iria chamar uma 2ª função que faria a limpeza para mim conforme os parâmetros. Acredito que essa não seja a solução com melhor performace, mas ainda tenho arrepios quando vejo um goto. Provável trauma de infância.

  • Marcelo Ossamu Honda

    O uso do goto descrito acima, está correto, inclusive a atenção que o autor faz ao uso indiscriminado.

    No livro abaixo, serve como referencia extra:

    Linux Device Drivers, 2nd Edition
    Chapter 2
    Building and Running Modules

    http://www.xml.com/ldd/chapter/book/ch02.html

  • Bruno Jesus

    Não é crime usar goto na minha opinião, lógico que existem as situações ridículas como em alguns exemplos apresentados =)

    Quando se programa para embarcados muitas vezes a utilização de gotos é necessária devido a restrição de tamanho do programa, que pode ser por exemplo 2Kb. Em uma arquitetura ARM de 32 bits usando o compilador IAR4 cada else consome 4 bytes, então muitas vezes é melhor atribuir o valor errado antes do IF e não usar o else, da mesma forma cada return consome 4 bytes então é muito mais eficiente marcar o valor de retorno em uma variável e saltar para um return só.

    O exemplo hipotético do RTC poderia ser reescrito sem malloc se a pilha permitisse uso de 60 bytes de buffer para os vetores de char, isso eliminaria a necessidade dos gotos. O uso do goto3 é desnecessário porque o fluxo do programa continuaria pra onde ele está saltando.

    Quando é utilizada otimização feita pelos compiladores é possível ver que muitas vezes são feitos gotos também.

  • Vitor R.

     Meus agradecimentos pelo artigo! Muito bem escrito e de fácil compreensão. Obrigado!

  • Nilson

    Olá sérgio, como vai?
    preciso de uma ajudinha sua.
    tenho um progaminha aqui que achei na internet, gostaria de saber se tem como mudar, tipo a linguaem foi feita para um relogio digital, com display sete segmentos ( de 4 digitos)
    só que o display e catodo comum e gostaria de saber se tem como mudar para anodo comum?
    abraço e parabéns pelo blog.

    • Olá Nilson,

      Sim, basta mudar a lógica do programa. Pinos do display que são setados devem ser resetados e vice-versa.

      Um abraço.

  • Guilherme Junio

    Olá Sérgio,
    Seu blog é excelente! tenho aprendido muitas coisas relacionadas à programação em c. Porém estou com um problema que não consegui encontrar nenhum recurso para resolver. Estou programando um PIC 16F628A e usando a interrupção por TIMER1 para fazer um contador simples. Durante a simulação no Proteus estou tendo problemas com estouro de pilha.
    Para compilar estou usando o CCS.

    o código segue abaixo, se puder me dizer o que pode estar causando esse problema lhe agradeço.

    #include “C:main.h”#include
    int16 a, mili;
    #int_TIMER1void TIMER1_isr(void) {   disable_interrupts(GLOBAL);   mili++;   a=mili;   clear_interrupt(INT_TIMER1);}
    void main(){      setup_timer_1(T1_INTERNAL|T1_DIV_BY_8);   set_timer1(0);   enable_interrupts(INT_TIMER1);   enable_interrupts(GLOBAL);   while(true){   if (a==100){      mili=0;      output_toggle(pin_b3);   }   }}

    • Olá Guilherme,

      Seu código perdeu toda a formatação, e infelizmente não consigo lê-lo. Coloque seu código em um site como o pastebin.com, ou mande em anexo.

      Um abraço.

  • Uma forma de evitar o uso de goto e ter uma funcionalidade interessante seria a de usar macros para esse fim. Já que basicamente, ao menos os exemplos que você deu, um copy/paste resolveria o problema.

    • Olá Francisco,

      É verdade, o uso de macros pode ser uma solução. Mas não evita duplicação de código e o fato do desenvolvedor esquecer de usá-las.

      Um abraço.

  • Davidson

    Realmente sempre evitei usá-lo, mas acaba que se for olhar a nível de máquina é tudo “goto” e ponto,o call seria apenas um jmp refinado, o não uso dele deve-se apenas a questão de legibilidade e de boas práticas.
    [ ]’s

  • Lucas Rybzinski Pinto

    Ola, sou bem iniciante em programação mas entendi do que se trata. No caso de um jogo onde o mapa e composto de matrizes e supondo que eu precise ir e voltar muitas vezes no código, como eu faria?

  • Dennis Ritchie

    Olá, caros amigos programadores em C.
    Logo de início eu quero já deixar umas perguntas pra vocês.

    Pra quê programar em baixo nível e não aproveitar o que a linguagem lhe oferece?

    Qual alternativa ao jmp/call em assembly que possa nos dar motivo para não usar goto em C?

    O que acontece no processador quando se usa um goto, if, else, switch, for, while, do-while em C?

    Por que o trecho de código em python abaixo é legível e o mesmo em C sofre preconceito e “não é legível”?

    Python:
    def alo():
    print
    ‘Alô Mundo’
    ————————————

    C:
    alo:
    puts(“Alô Mundo”);

    Não é porque o goto salta pra qualquer lugar do programa que o mesmo irá sair fazendo saltos por conta própria.
    Se o programador não sabe deixar o código organizado com uso de goto, então que não o utilize, mas ficar criticando o seu uso não é certo.

    Se Dennis M. Ritchie adicionou o “goto” a linguagem C, foi por um bom motivo.

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.