Programando em C Volume I: Fundamentos — Perguntas mais Frequentes

Conteúdo

Capítulo 1 — Introdução à Linguagem de Programação C

P. Onde posso obter uma cópia do padrão C99?

R. Você pode obter cópia "draft" do padrão ISO na internet. Apesar de ser "draft", existe pouca diferença entre esta versão e a versão final (que custa U$18). O nome completo do padrão é ISO/IEC 9899:1999. Pesquise no Google.

P. Acho que o seguinte exemplo apresenta um erro:

int  i, j = 4;
i = j * j++;

Mesmo que j++ seja avaliado primeiro, o resultado não será 16 (já que o operador ++ é sufixo)?

R. Não existe erro neste exemplo. O que gera o problema em questão não é o fato de se estar usando a versão sufixa do operador ++, mas sim o fato de este operador (em qualquer versão) possuir efeito colateral e, além disso, a variável que sofre efeito colateral ser utilizada na mesma expressão. Vou tentar explicar novamente a razão para os dois resultados possíveis.

Caso 1: O operando da esquerda (j) é avaliado primeiro. Neste caso, j vale 4 que é valor com o qual a variável foi iniciada e j++ vale também 4, devido à definição do resultado do operador sufixo. Assim, i valerá 4*4 = 16.

Caso 2: O operando da direita (j++) é avaliado primeiro. Neste caso, novamente, j++ vale 4. Mas, agora, j vale 5 que é valor com o qual a variável foi iniciada mais 1, devido ao efeito colateral sofrido pela variável quando a expressão j++ foi avaliada (lembre-se que este efeito colateral independe do fato de se estar usando a versão prefixa ou sufixa de ++). Assim, i valerá 5*4 = 20.

O mesmo problema ocorreria se a versão prefixa do operador fosse utilizada; i.e., se a instrução fosse i = j*++j. Neste caso, se j fosse avaliado primeiro, o resultado seria 4*5 = 20; se ++j fosse avaliado primeiro, o resultado seria 5*5 = 25. Os resultados são diferentes neste caso, mas o problema é o mesmo: existem dois resultados possíveis dependentes do compilador utilizado.

P. Eu queria alguma uma função que fizesse uma equivalência entre o código ASCII e um tipo inteiro.

R. Um compilador de C não usa necessariamente código ASCII. Este código de caracteres é realmente o mais usado, mas não se pode fazer esta suposição.

P. Para programar em C é necessário ter uma cópia da tabela ASCII em mãos?

R. Gostaria de entender esta obsessão apresentada por alguns programadores iniciantes com relação ao código ASCII. Para escrever um programa em C, você nem sequer precisa saber qual é o código de caracteres utilizado. O próprio compilador faz a devida tradução para você. Mas, o problema maior não é o trabalho adicional de ter que consultar uma tabela de códigos ASCII. O pior é mesmo é que fazer isto torna o programa não portável.

P. A instrução j = (i + 1) * (i = 1); não é portável porque compiladores diferentes podem ter precedências (ou associatividades) diferentes. Certo?

R. Errado. Nem precedência nem associatividade varia de compilador para compilador, já que estas propriedades de operadores são bem especificadas no padrão da linguagem C. O que pode variar de um compilador para outro é ordem de avaliação, que é uma propriedade completamente diferente de precedência ou associatividade. Apenas alguns operadores possuem ordem de avaliação especificada pelo padrão ISO da linguagem C.

Capítulo 2 — Programas Mono-Arquivos

P. Consegui compilar o programa, mas não consegui executá-lo de modo satisfatório. Não está aparecendo nada na tela do programa executável.

R. Leia a Seção 2.5.

P. Por que você é contrário ao uso de getchar() antes da instrução return para "segurar a tela"?

R. Pode-se usar eventualmente usar getchar() com este propósito, desde que se saiba o que está fazendo. Errado é achar que todo programa escrito em C precisa de uma instrução para "segurar a tela".

P. Que especificador de formato devo usar na função printf para o tipo long double?

R. O especificador de formato para long double é %Lf (com "L" maiúsculo) ao invés de %lf (com "l" minúsculo).

P. No teste que fiz abaixo, quando digito o primeiro caractere e teclo ENTER, o segundo recebe algo. (Será o ENTER?) Como faço para que o programa receba apenas uma letra (sem precisar teclar o ENTER)?

int main (void)
{
   char a,b;

   a=getchar();
   b=getchar();

   printf("o 1o. e'%c o 2o. e' %c ",a, b);

   return 0;
}

Uma solução que achei foi colocar um getchar() antes de b=getchar(). O que você me diz sobre isso?

R. Leia e releia a Seção 2.6 do livro.

P. Por que não posso usar a função fflush() para limpar o buffer de entrada ao invés de LimpaBuffer()?

R. O uso de fflush() com entrada (como é o caso de stdin) não é especificado e portanto não é portável. Quer dizer, pode ser que fflush(stdin) funcione bem numa implementação de C e não funcione em outra.

P. A função LimpaBuffer(), apresentada no livro não poderia ser simplesmente substituída por fflush(stdin)? Ou seja, descarregar todo conteúdo da entrada padrão?

R. Não. Este uso da função fflush() não é especificado pelo padrão da linguagem C. Segundo este padrão, fflush() deve ser usada apenas com streams de saída. Mas, alguns compiladores realmente implementam seu uso com streams de entrada, como stdin, o que explica o fato de esta construção ser vista com frequência em alguns livros.

P. Onde encontro uma lista completa de especificadores de formato para printf e scanf?

P. Qual é o especificador de formato usado para ler um valor de tal tipo?

R. Consulte o Apêndice B do livro. Se não encontrar o que procura, consulte o Apêndice B do Volume II.

P. Que arquivo é necessário incluir para fazer uso da função LimpaBuffer()?

R. Esta função não existe na biblioteca padrão de C. Simplesmente copie-a para seu programa

P. Estou tentando resolver o exercício de programação EP2.6, mas não consigo encontrar as constantes simbólicas que representam os valores mínimos dos tipos unsigned short , unsigned long e unsigned long long.

R. Não irá encontrá-las porque elas não existem. Foi só uma pegadinha. O valor mínimo de qualquer tipo unsigned é zero. Portanto, estas constantes são desnecessárias.

Capítulo 3 — Funções

Capítulo 4 — Programas Multiarquivos

P. Qual é o critério que o compilador usa para alocar as variáveis em registradores, se o numero de variáveis declaradas como register for maior que o numero de registradores disponíveis?

R. Isso depende do fabricante do compilador. O padrão ISO não especifica nenhum critério. Uma estratégia comum utilizada é alocar registros até que estes estejam preenchidos de acordo com a ordem de solicitação no programa, mas o compilador pode simplesmente ignorar todas as declarações register do programador.

P. Não entendi para que serve volatile. Você poderia me explicar resumidamente, em poucas palavras.

R. Em poucas palavras: volatile serve para evitar que o compilador ache que uma variável permanece imutável em dada situação e tente tratá-la como tal. Os exemplos práticos de uso de volatile são raros.

P. No arquivo molde.c, tem-se:

/* Inclua aqui outros  arquivos de cabeçalhos necessários NESTE arquivo */

No arquivo molde.h, tem-se:

/* Inclua aqui os arquivos  de cabeçalhos necessários NESTE arquivo */

É necessário incluir os arquivos de cabeçalhos nos dois arquivos? Como o molde.h é incluído em molde.c, não estou incluindo esses arquivos duas vezes?

R. O significado é exatamente este: você inclui os arquivos de cabeçalho necessários no próprio arquivo. Os cabeçalhos incluídos no arquivo de cabeçalho (.h) de um módulo não são usualmente os mesmos incluídos no arquivo de implementação (.c) do módulo. Por exemplo, se o arquivo de implementação de um módulo utiliza a função printf(), ele deve incluir stdio.h, mas provavelmente o arquivo de cabeçalho do módulo não incluirá stdio.h. Evidentemente, existe a possibilidade de os dois arquivos de um módulo tentarem incluir o mesmo arquivo. Por exemplo, o arquivo de cabeçalho inclui o arquivo arq.h (por necessidade!) e o arquivo de implementação também inclui o arquivo arq.h (também porque precisa dele e talvez pelo fato de o programador não conhecer detalhes sobre o arquivo de cabeçalho do módulo). Numa situação como essa, o arquivo de implementação estará tentando incluir arq.h duas vezes: a primeira vez com seu próprio include e a segunda vez com o include de seu arquivo de cabeçalho que por sua vez já inclui arq.h (ou o contrário). É exatamente para evitar inclusão múltipla de um arquivo de cabeçalho que são utilizadas aquelas diretivas de compilação condicional envolvendo arquivos de cabeçalho:

#ifndef  __arq_H__
#define  __arq_H__
[Conteúdo  efetivo do arquivo arq.h ]
#endif 

Na primeira inclusão de arq.h num outro arquivo a macro __arq_H__ é definida. Na segunda tentativa de inclusão, a diretiva #ifndef __arq_H__ (que significa "se a macro __arq_H__ não é definida") não é satisfeita e todo o conteúdo efetivo deste arquivo é saltado evitando assim a múltipla inclusão.

P. Não entendi a diferença entre escopo de função de escopo de bloco.

R. Um identificador tem escopo de bloco quando ele é válido em um bloco a partir de seu ponto de declaração. Por outro, um identificador tem escopo de função quando ele é válido em todo o corpo da função (que é um bloco), independentemente de seu local de declaração e do fato de ele estar dentro de um bloco interno ao corpo da função. Portanto, apenas rótulos de instruções têm este tipo de escopo. Isto significa que, dentro de uma função, você pode fazer referência a um rótulo não importando aonde ele é definido nesta função.

Capítulo 5 — O Pré-Processador de C

P. Escrevi a seguinte macro para calcular o valor absoluto de um número.

#include  <math.h>
#define  ABS(x) (sqrt((x)*(x)))

Mas ela não funciona bem.

R. O problema mais sério é que pode ocorrer com esta macro é overflow. A função sqrt() recebe um número multiplicado por si mesmo e o resultado da multiplicação pode gerar overflow, mesmo que o número não seja tão grande assim. O segundo problema é de aproximação, pois a função sqrt() recebe como argumento um valor do tipo double. Portanto, se um número inteiro for usado com a macro, o resultado pode ser aproximado. O modo correto de definir esta macro é:

#define ABS(x) ((x) > 0 ? (x) : -(x))

Capítulo 6 — Legibilidade e Depuração de Programas

P. Por que você dá tanta importância a legibilidade?

R. Porque legibilidade é realmente essencial. Programas ilegíveis são difíceis de entender até mesmo para o próprio programador. Muitas vezes, escrevi programas às pressas que, depois, não consegui entender a lógica que havia usado. Talvez, os programas que você deve estar escrevendo agora sejam simples demais para não entender (mesmo sem um bom estilo), mas é tentando escrever programas cada vez mais legíveis que se adquire disciplina e estilo.

P. Qual dos dois estilos é mais recomendável: aquele em que as variáveis são declaradas no inicio de uma função ou aquele em que estão em cada case de uma instrução switch. Eu prefiro declarar as variáveis de cada case em cada case, principalmente por facilitar os comentários. Qual a sua opinião?

R. Tanto faz. Eu pessoalmente prefiro declarar no início dos blocos, mas isto é apenas resquício de programação em linguagens que não permitem declarações em outros pontos. Há muitos programadores que defendem que é mais natural declarar variáveis próximas ao local aonde elas serão utilizadas.

P. Gostaria de saber se há um padrão de codificação internacional ou se você recomenda algum.

R. Acredito que você esteja interessado em "estilo de codificação em C" e quanto a isso a resposta é não, pelo menos no que diz respeito a C. Isto é, quando se fala em "coding standards"  se  quer dizer realmente dizer "convenção de codificação" e não um padrão homologado por uma organização como, por exemplo, ISO. Estas convenções são criadas por fabricantes de software e são comummente denominadas "house styles" ou algo parecido.

Topo

Capítulo 7 — Arrays e Ponteiros

Capítulo 8 — Strings

P. Gostaria de saber se C possui função do  padrão ISO para transformar todos os caracteres de um string qualquer em maiúsculas.

R. Não existe tal função em C, mas é fácil implementar uma função que faz isso:

#include  <ctype.h> 
char  *UpStr(char *str)
{
    while (*str = toupper(*str))
       str++;
    return str;
}

P. Que função utilizo para transformar uma string em inteiro?

R. Use atoi(), se o interior for do tipo int ou atol(), se for do tipo long. Nos dois casos, #include <stdlib.h>.

Capítulo 9 — Estruturas, Uniões e Enumerações

Capítulo 10 — Construções Complexas

Capítulo 11 — Alocação Dinâmica de Memória

Capítulo 12 — Processamento de Arquivos

P. Você poderia me dar um exemplo em que  o fgets() faz a leitura e o stream está vazio?

R. A única possibilidade de isto ocorrer é quando o stream representa um arquivo. Não vejo chance de isto acontecer com o stream de entrada padrão, pois, para se enviar qualquer dado para um programa, é necessário digitar pelo menos ENTER ou RETURN e, neste caso, o stream contém um caractere.

P. Os modos de abertura de arquivo são muito complicados para memorizar. Você poderia resumi-los?

R. Espero que a tabela a seguir seja útil para clarificar as semelhanças e diferenças entre os diversos modos de abertura de arquivo.

Especificação

Operação

Arquivo Deve Existir?

Conteúdo É Sempre Preservado?

r

Leitura

Sim

Sim

w

Escrita

Não

Não (Nunca)

a

Escrita

Não

Sim

r+

Leitura e escrita

Sim

Não

w+

Leitura e escrita

Não

Não (Nunca)

a+

Leitura e escrita

Não

Sim

Capítulo 13 — Programação de Baixo Nível em C

P. Para que serve a  constante CHAR_BIT, se o número de bits em um byte é sempre 8?

R. O padrão da linguagem C não especifica quantos bits tem um byte. O que se afirma apenas é que um char sempre ocupa um byte. (Em outras palavras, char e byte são sinônimos.)

Miscelânea

P. Preciso fazer um programa em C que chame outro  programa.

R. Use a função system() (#include <stdlib.h>). Maiores detalhes são apresentados no Volume II.

P. Você acha que seu livro é suficiente para me tornar um bom programador?

R. Ser um bom programador não é tarefa fácil, porque programação em si é uma atividades difícil. Eu sempre fui apaixonado por programação, mas quando vejo certos trechos de programas que escrevi dez anos atrás, acho ridículo que tenha escrito aquilo (naquela época, eu já ensinava e me considerava bom programador) e percebo o quanto tive que estudar e praticar para evoluir tanto. Programação é como musculação. Quer dizer, todo mundo quer ter um corpo bonito e musculoso, mas poucos entendem que isto requer muito tempo e sacrifício. Programação também é assim. Não existem atalhos.

P. Como faço para testar se uma variável do tipo float, recebeu um valor "NaN"? A mesma duvida tenho quanto a "Inf", mas creio que seja o mesmo raciocínio.

R. A macro isnan() (C99) verifica se o argumento é NaN e a macro isinf() (C99) verifica se o parâmetro de ponto-flutuante é +∞ ou -∞. Estas macros são definidas em <math.h>. Maiores detalhes encontram-se no Volume II.

P. Gostaria de saber onde encontrar algo sobre o compilador GCC for Unix.

R. Os compiladores GNU podem ser encontrados na origem: http://gcc.gnu.org/

P. Estou com problemas usando o compilador (depurador, lint, profiler, etc) X.

R. Procure informações sobre o compilador (depurador, lint, profiler, etc) X no site do fabricante ou em listas de discussão na internet.

P. Ouvi outros comentários dizendo que C é importante sim, mas como pré-requisito pois C++ e Java são as linguagens que usaremos no futuro. Minha duvida é essa: C é apenas um degrau para C++ e Java?

R. De modo algum. Apesar de C++ e Java serem mais modernas do que C, existem muitas situações em programação nas quais é mais conveniente usar C do que C++ ou Java. 

P. Estou com uma pequena duvida no caso de escolher um compilador C. Gostaria de saber se tem algum problema em usar o Compilador C++ da Borland para compilar algo escrito apenas em  C ou ele não funciona com C normal?

R. Qualquer compilador "decente" de C++ também compila programas escritos em C. Normalmente, estes compiladores entendem que arquivos-fonte com extensão .c são parte de programas em C, enquanto que arquivos com extensão .cpp são arquivos de C++.

P. A linguagem C permite programar utilizando Banco de Dados? Qual? Como?

R. Sim, porque isto não depende da linguagem em si, mas sim do sistema de banco de dados utilizado (i.e., se a API do SGBD suporta uma determinada linguagem). Por exemplo, o SGBD MySQL usa C, SQL Server usa Visual Basic, etc.

P. Como programar utilizando interface gráfica? A linguagem C permite isso, ou temos que utilizar o C++ e Java?

R. Podem-se construir interfaces gráficas em C, mas esta é uma tarefa mais adequada para uma linguagem orientada a objetos (principalmente pela facilidade oferecida para reuso de componentes da interface), como C++ e Java. Java leva vantagem nesta tarefa com relação a outras linguagens, pois possui uma biblioteca de classes para construção de interfaces gráficas.

P. O livro não contém solução para os exercícios de programação. Como sei se um exercício de programação foi corretamente resolvido?

R. Existe um conjunto de regras a serem seguidas apresentadas no Prefácio e no Capítulo 6. Se você seguir essas regras e testar o programa exaustivamente para verificar se ele funciona adequadamente, provavelmente o programa estará bom.

P. Qual biblioteca e quais funções usar para incluir uma imagem em um programa em C, bem como para editá-la (alterar a figura)? Eu ouvir falar de uma tal Allegro. Você conhece?

R. Não existe nenhuma função na biblioteca padrão de C capaz de fazer isto que você deseja, mas existem várias bibliotecas comerciais e gratuitas dedicadas a computação gráfica que servem para este propósito. Allegro trata-se de uma biblioteca voltada para programação de jogos e, como jogos são tipicamente desenvolvidos em ambientes gráficos, deve conter funções que fazem aquilo que você procura. No site de Allegro pode-se fazer download do código-fonte da biblioteca pára várias plataformas. O endereço é: http://www.talula.demon.co.uk/allegro/.

P. Estou com dificuldades em encontrar uma forma de posicionar o cursor em qualquer ponto da tela utilizando C padrão. Seria fazer o mesmo que a função gotoxy() da biblioteca conio. Obviamente utilizar a conio não seria nada muito elegante, nem portável. Teria alguma outra sugestão para resolver esse problema de forma mais elegante?

R. Infelizmente, não há nenhuma forma de fazer isso de modo portável, pois a linguagem C nem sequer assume a existência de tela.

Mas, nem tudo está perdido. Na prática, qualquer plataforma possui uma função como gotoxy() (talvez com um nome diferente). Para minimizar o problema de portabilidade, a técnica utilizada é encapsular a chamada de gotoxy() (ou qualquer outra função não-portável)  numa outra função ou macro que apenas chama a função gotoxy(). Por exemplo:

#define  POSICIONA(x, y) gotoxy((x), (y))

Então, ao invés de chamar gotoxy(), você chama POSICIONA(). A vantagem desta abordagem é que se precisar portar o programa para outra plataforma, tudo que você tem que fazer é substituir, se necessário, a definição desta macro para chamar a função usada nesta plataforma.