4. Portando e compilando

4.1. Símbolos definidos automaticamente

Você pode verificar quais símbolos a sua versão do gcc define automaticamente executando-o com o switch -v. Por exemplo, o meu faz:

    $ echo 'main(){printf("hello world\n");}' | gcc -E -v -
    Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
    gcc version 2.7.2
     /usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef
    -D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux
    -D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386
    -D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386)
    -Amachine(i386) -D__i486__ -


Se você está escrevendo código que usa característica específicas do Linux, é uma boa idéia cercar os elementos não portáveis com

    #ifdef __linux__
    /* ... coisas legais ... */
    #endif /* linux */


Use __linux__ para este propósito, não linux. Ainda que o último esteja definido, ele não é compatível com POSIX.

4.2. Invocação do Compilador

A documentação para as opções do compilador está na página info do gcc (no Emacs, use C-h i e então selecione a opção `gcc'). O seu distribuidor pode não ter incluído isto com o seu sistema, ou você pode ter uma versão antiga; a melhor coisa a fazer neste caso é efetuar o download do arquivo de fontes do gcc a partir de ou um dos seus mirrors, e copiá-los de lá.

A página de manual do gcc (gcc.1) está, de modo geral, desatualizada. Ela o avisará disso quando você tentar olhar para ela.

4.2.1. Flags do compilador

gcc pode ser forçado a otimizar o código gerado adicionando -On na linha de comando, onde n é um inteiro pequeno opcional. Valores significativos de n, e o seu efeito exato, variam de acordo com cada versão, mas tipicamente ele varia de 0 (nenhuma optimização) até 2 (muitas) or 3 (muitas e muitas).

Intermamente, o gcc os traduz para uma série de opções -f e -m. Você pode ver exatamente quais níveis -O correspondem a quais opções executando o gcc com a flag -v e a flag (não documentada) -Q. Por exemplo, para -O2, o meu diz

    enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks
    -fexpensive-optimizations
             -fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline
             -fcaller-saves -fpcc-struct-return -frerun-cse-after-loop
             -fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float
             -mno-386 -m486 -mieee-fp -mfp-ret-in-387


Usar um nível de otimização mais alto do que o seu compilador suporta (por exemplo -O6) terá exatamente o mesmo efeito que usar o nível mais alto que ele realmente suporta. Distribuir código configurado para compilar desta maneira é uma má idéia entretanto --- se otimizações adicionais forem incorporadas em versões futuras, você (ou seus usuários) podem descobrir que elas danificam o seu código.

Usuários do gcc 2.7.0 até 2.7.2 devem notar que existe um bug em -O2. Especificamente, a redução de resistência não funciona. Uma correção pode ser obtida para consertar isto se você se dispuser a recompilar o gcc, senão certifique-se de que você sempre compila com -fno-strength-reduce

4.2.1.1. Detalhes específicos de cada processador

Existem outras flags -m que não são ativadas por qualquer tipo de -O mas são úteis de qualquer forma. As principais entre estas são -m386 e -m486, as quais dizem ao gcc para favorecer os 386 e 486 respectivamente. Código compilado com uma destas ainda irá funcionar com a outra; código para 486 é maior mas, por outro lado, não é mais lento em um 386.

Não existe atualmente um -mpentium ou -m586. Linus sugere usar -m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2, para obter as otimizações de código para 486 mas sem os grandes espaços para alinhamento (que o pentium não necessita). Michael Meissner (da Cygnus) diz

"Meu palpite é que -mno-strength-reduce também resulta em código mais rápido em um x86 (note, eu não estou falando sobre o bug de redução de resistência, que é outra questão). Isto acontece porque o x86 é bastante limitado no número de registradores (e o método do GCC de agrupar os registradores em registradores de transbordamento versus outros registradores não ajuda). A redução de resistência tipicamente resulta no uso de registradores adicionais para substituir multiplicações com adições. Eu também suspeito que -fcaller-saves também pode ser uma perda." "Outro palpite é que -fomit-frame-pointer pode ou não ser uma vantagem. Por um lado, ele pode significar que outro registro está disponível para alocação. Por outro lado, a maneira que um x86 codifica o seu conjunto de instruções, significa que endereços relativos de pilha ocupam mais espaço do que endereços relativos de quadros, o que significa um pouco menos de lcache disponível para o programa. Também, -fomit-frame-pointer, significa que o compilador tem que ajustar constantemente o ponteiro da pilha após chamadas, enquanto que com um quadro, ele pode deixar a pilha acumular por algumas chamadas."

A palavra final neste assunto vêm de Linus novamente:

"Note que se você quer obter um ótimo desempenho, não acredite em mim: teste. Existem muitas opções do compilador gcc, e pode ser que um conjunto particular delas forneça a melhor otimização para você. "

4.2.2. Erro interno do compilador: cc1 recebeu um sinal fatal 11

O sinal 11 é o SIGSEGV, ou `violação de segmentação'. Usualmente ele significa que o programa deixou os seus ponteiros confusos e tentou escrever em memória que ele não possuia. Então, ele pode ser um bug do gcc.

gcc é entretanto, um software bem testado e confiável, na maior parte. Ele também usa um grande número de estruturas de dados complexas, e uma enorme quantidade de ponteiros. Em resumo, é o testador de RAM mais rigoroso largamente disponível. Se você não pode duplicar o bug --- se ele não para no mesmo lugar quando você reinicia a compilação --- ele é quase certamente um problema com o seu hardware (CPU, memória, placa mãe ou cache). Não alegue que é um bug porque o seu computador passa os testes de inicialização ou executa Windows sem problemas ou qualquer outra coisa; esses 'testes' são comumente e corretamente tidos como sem valor. E não alegue que é um bug porque uma compilação do kernel sempre para durante `make zImage' --- é claro que ela o fará! `make zImage' está provavelmente compilando mais de 200 arquivos; você está olhando para um lugar ligeiramente menor do que aquele.

Se você pode duplicar o bug, e (melhor) pode produzir um pequeno programa que o demonstre, você pode enviá-lo como um relatório de bug para a FSF, ou para a lista de correio linux-gcc. Veja a documentação do gcc para detalhes de quais informações eles precisam exatamente.

4.3. Portabilidade

Ultimamente, tem-se dito que, se alguma coisa não foi portada para o Linux, então não vale a pena possuí-la :-)

Falando sério, em geral somente pequenas mudanças são necessárias nos fontes para se adequar aos 100% de conformidade do Linux ao padrão POSIX. Também é valioso retornar quaisquer mudanças para os autores do código de forma que, no futuro, somente seja necessário executar 'make' para se ter um executável que funcione.

4.3.1. BSDismos (incluindo bsd&lowbar;ioctl, daemon e <sgtty.h>)

Você pode compilar o seu programa com -I/usr/include/bsd e linka-lo com -lbsd (isto é, adicione -I/usr/include/bsd a CFLAGS e -lbsd à linha LDFLAGS no seu arquivo Makefile). Não existe mais a necessidade de adicionar -D&lowbar;&lowbar;USE&lowbar;BSD&lowbar;SIGNAL se você quer um comportamento de sinal do tipo BSD, porque você recebe isto automaticamenteas quando você tem -I/usr/include/bsd e inclue <signal.h>.

4.3.2. Sinais `desaparecidos' (SIGBUS, SIGEMT, SIGIOT, SIGTRAP, SIGSYS etc)

O Linux segue o padrão POSIX. Estes não são sinais definidos em POSIX --- ISO/IEC 9945-1:1990 (IEEE Std 1003.1-1990), paragraph B.3.3.1.1 diz:

"``Os sinais SIGBUS, SIGEMT, SIGIOT, SIGTRAP, e SIGSYS foram omitidos de POSIX.1 porque o seu comportamento depende da implementação e não pôde ser adequadamente categorizado. Implementações em conformidade podem emitir estes sinais, mas devem documentar as circunstâncias sob as quais eles são emitidos e salientar quaisquer restrições relativas à sua emissão.''"

A forma barata e agradável de corrigir isto é redefinir estes sinais para SIGUNUSED. A forma correta é envolver o código que manipula-os com os apropriados &num;ifdefs:

    #ifdef SIGSYS
    /* ... non-posix SIGSYS code here .... */
    #endif


4.3.3. Código no padrão K & R

O GCC é um compilador ANSI; muito código existente não é ANSI. Realmente não há muito que pode ser feito a esse respeito, exceto adicionar -traditional às flags do compilador. Existe um certo grau de controle mais detalhado sobre os tipos de dano cerebral a serem emulados; consulte a página info do gcc.

Note que -traditional tem outros efeitos além de apenas modificar a linguagem que o gcc aceita. Por exemplo, ele ativa -fwritable-strings, o que move as constantes string para o espaço de dados (para fora do espaço de texto, onde elas não podem ser sobreescritas). Isto aumenta o espaço de memória ocupado pelo programa.

4.3.4. Conflito dos símbolos do preprocessador com protótipos no código

Um dos problemas mais frequentes é que algumas funções comuns são definidas como macros nos arquivos de cabeçalho do Linux e o preprocessador irá recusar-se a analisar definições de protótipos similares no código. Exemplos comuns são atoi() e atol().

4.3.5. sprintf()

Uma coisa à qual é preciso estar atento, especialmente quando portando a partir do SunOS, é que sprintf(string, fmt, ...) retorna um ponteiro para uma string em muitos tipos de unix, enquanto que no Linux (seguindo ANSI) retorna o número de caracteres que foram colocados na string.

4.3.6. fcntl e amigos. Onde estão as definições dos FD&lowbar;*?

Em <sys/time.h>. Se você está usando fcntl você provavelmente quer incluir <unistd.h> também, para obter os protótipos.

De modo geral, a página de manual para uma função lista os &num;includes necessários na sua seção SYNOPSIS.

4.3.7. O timeout de select(). Os programas começam a travar.

A página de manual do BSD para select(2) costumava dizer "select() deve provavelmente retornar o tempo restante do timeout original, se existir, através da modificação do valor de tempo no local. Isto pode ser implementado em versões futuras do sistema. Assim, não é prudente assumir que o ponteiro de timeout não será modificado pela chamada a select()."

Algumas versões do Linux efetuam esta modificação. Algumas não. É incrivelmente imprudente assumir um comportamento ou outro.

Para corrigir, coloque o valor de timeout naquela estrutura cada vez que você chamar select(). Altere código como este

          struct timeval timeout;
          timeout.tv_sec = 1; timeout.tv_usec = 0;
          while (some_condition)
                select(n,readfds,writefds,exceptfds,&timeout);
para, digamos,
          struct timeval timeout;
          while (some_condition) {
                timeout.tv_sec = 1; timeout.tv_usec = 0;
                select(n,readfds,writefds,exceptfds,&timeout);
          }


Algumas versões do Mosaic foram, numa certa época, notáveis por este problema. A velocidade da animação do globo giratório era inversamente relacionada à velocidade que os dados estavam chegando da rede!

4.3.8. Chamadas de sistema interrompidas.

4.3.8.1. Sintoma:

Quando um programa é parado usando Ctrl-Z e então reiniciado - ou em outras situações que geram sinais: interrupção com Ctrl-C, finalização de um processo filho, etc. - ele reclama sobre "chamada de sistema interrompida" ou "escrita: erro desconhecido" ou coisas semelhantes.

4.3.8.2. Problema:

Sistemas POSIX verificam a ocorrência de sinais um pouco mais frequentemente do que alguns sistemas unix mais antigos. O Linux pode executar manipuladores de sinais ---



  • assincronamente (a qualquer momento)

  • ao retornar de qualquer chamada de sistema

  • durante a execução das seguintes chamadas de sistema: select(), pause(), connect(), accept(), read() em terminais, sockets, pipes ou arquivos em /proc, write() em terminais, sockets, pipes ou a impressora de linha, open() em FIFOs, PTYs ou linhas seriais, ioctl() em terminais, fcntl() com comando F&lowbar;SETLKW, wait4(), syslog(), quaisquer operações TCP ou NFS.

Para outros sistemas operacionais você pode ter de incluir as chamadas de sistema creat(), close(), getmsg(), putmsg(), msgrcv(), msgsnd(), recv(), send(), wait(), waitpid(), wait3(), tcdrain(), sigpause(), semop() nesta lista

Se um sinal (para o qual o programa instalou um manipulador) ocorrer durante uma chamada de sistema, o manipulador é chamado. Quando o manipulador retorna (para a chamada de sistema) ela detecta que foi interrompida, e retorna imediatamente com o valor -1 e errno = EINTR. O programa não está esperando que isto ocorra e termina o processamento.

Você pode escolher entre duas correções

(1) Para cada manipulador de sinais que você instalar, adicione SA&lowbar;RESTART às flags de sigaction. Por exemplo, mude

      signal (sig_nr, my_signal_handler);
para
      signal (sig_nr, my_signal_handler);
      { struct sigaction sa;
        sigaction (sig_nr, (struct sigaction *)0, &sa);
    #ifdef SA_RESTART
        sa.sa_flags |= SA_RESTART;
    #endif
    #ifdef SA_INTERRUPT
        sa.sa_flags &= ~ SA_INTERRUPT;
    #endif
        sigaction (sig_nr, &sa, (struct sigaction *)0);
      }


Note que enquanto isto se aplica à maioria das chamadas de sistema, você ainda tem de verificar, por si mesmo, a ocorrência de EINTR em read(), write(), ioctl(), select(), pause() e connect(). Veja abaixo.

(2) Verifique a ocorrência de EINTR explicitamente, você mesmo:

Aqui estão dois exemplos para read() e ioctl(),

Trecho original de código usando read()

    int result;
    while (len > 0) {
      result = read(fd,buffer,len);
      if (result < 0) break;
      buffer += result; len -= result;
    }
torna-se

    int result;
    while (len > 0) {
      result = read(fd,buffer,len);
      if (result < 0) { if (errno != EINTR) break; }
      else { buffer += result; len -= result; }
    }
   
e um trecho de código usando ioctl()

    int result;
    result = ioctl(fd,cmd,addr);
torna-se
    int result;
    do { result = ioctl(fd,cmd,addr); }
    while ((result == -1) && (errno == EINTR));


Note que em algumas versões do Unix tipo BSD o comportamento padrão é reiniciar as chamadas de sistema. Para fazer com que elas sejam interrompidas, você tem de usar a flag SV&lowbar;INTERRUPT ou SA&lowbar;INTERRUPT.

4.3.9. Strings alteráveis (programa causa falhas de segmentação aleatoriamente)

O GCC tem uma visão otimista dos seus usuários, acreditando que eles desejam que as constantes string sejam exatamente isto --- constantes. Assim, ele armazena-as na área de texto (código) do programa, onde elas podem ser paginadas, a partir da imagem em disco do programa (ao invés de ocupar a área de swap), e qualquer tentativa de reescreve-las causará uma falha de segmentação. Esta é uma característica!

Isto pode causar um problema para programas antigos que, por exemplo, chamam mktemp() com uma constante string como argumento. mktemp() tenta reescrever o seu argumento no lugar.

Para corrigir, ou (a) compile com -fwritable-strings, para fazer com que o gcc coloque as contantes no espaço de dados ou, (b) reescreva os trechos em desacordo para alocar uma string não-constante e use strcpy para copiar os dados dentro dela antes de chamar a função.

4.3.10. Por que a chamada a execl() falha?

Porque você está chamando-a de forma errada. O primeiro argumento para execl é o programa que você quer executar. O segundo argumento e subsequentes tornam-se a matriz argv do programa que você está chamando. Lembre-se: argv[0] é tradicionalmente definido mesmo quando um programa é executado com `nenhum' argumento. Então, você devia estar escrevendo

    execl("/bin/ls","ls",NULL);
e não apenas
    execl("/bin/ls", NULL);


Executar o programa sem qualquer argumento é interpretado como um convite para exibir as suas dependências de bibliotecas dinâmicas, pelo menos ao usar a.out. ELF trabalha de forma diferente.

(Se você quer esta informação sobre bibliotecas, existem interfaces mais simples; veja a seção sobre carregamento dinâmico, ou a página de manual para ldd).