Вы находитесь на странице: 1из 7

Material Complementar – EA869 (Prof.

Von Zuben – DCA/FEEC/Unicamp) 1

Programando em Linguagem ANSI C


1. Compilando um programa em linguagem C no UNIX
O processo de geração de um programa executável envolve pelo menos uma das seguintes
etapas: preprocessamento, compilação, montagem, ligação.
Programas-fonte em C devem possuir a extensão “.c”, enquanto que programas-objeto
(preprocessados, compilados, montados, mas não ligados) devem possuir a extensão “.o”. Extensões “.C”
e “.cc” são geralmente reservadas para programas-fonte em C++.
Um comando UNIX do tipo $cc fonte.c ou $gcc fonte.c vai executar os 4 estágios
mencionados e o arquivo final (arquivo executável) receberá o nome default a.out. O compilador cc
geralmente tende a ser específico para cada máquina e versão do sistema operacional. Tanto cc como
gcc aceitam o padrão ANSI, mas não estão restritos unicamente a ele. Se o usuário quiser fornecer o
nome do arquivo executável (procedimento altamente recomendado), por exemplo arqexec, ele deve
utilizar o seguinte comando com opção “-o”: $cc fonte.c -o arqexec. Caso se queira debugar o
programa utilizando o comando dbx (procedimento útil para detectar erros na fase de execução do
programa), deve-se incluir a opção -g. Também é possível compilar um programa-fonte de forma a gerar
apenas o programa-objeto. Para tanto, utilize o comando $cc -c fonte.c.
Há casos em que é necessário indicar ao compilador quais bibliotecas (além da biblioteca padrão
da linguagem) contêm funções que estarão sendo executadas em seu programa. Para tanto, utilize a opção
-l seguida do nome da biblioteca.
Embora os compiladores C mais modernos já sejam capazes de diagnosticar quase todos os
problemas com o código-fonte, lint é o comando geralmente utilizado para realizar uma verificação
formal de alto nível, capaz de diagnosticar problemas de programação secundários ou não-vitais, mas que
podem produzir efeitos inesperados ou não-portáveis.
Quando um programa é composto por vários arquivos-fonte, o programa make pode ser
utilizado para realizar automaticamente a ligação dos arquivos-objeto correspondentes, recompilando
aqueles que foram modificados após a última criação do arquivo executável.
O manual on-line man contém a documentação completa sobre os comandos cc e gcc.

2. Mais alguns conceitos básicos


2.1. A função principal e seus argumentos
Pata todo programa escrito em C, independente de haver ou não retorno de valores, a função
principal main tem que ser declarada como retornando inteiro. Declarações como void main()
podem causar problemas ao processo responsável pela execução do código. Com isso, são válidas
declarações como:
main()
int main()

Além disso, toda vez que for necessário ou desejado passar argumentos na linha de comando de
um programa escrito em C, deve-se declarar a função principal como:
main(int argc,char *argv[]) /* OU int main(int argc,char *argv[]) */

A primeira variável declarada, convencionalmente chamada argc, conterá o número de


argumentos com que o programa foi chamado. Já a segunda variável, convencionalmente chamada argv,
é um apontador para um arranjo de cadeias de caracteres, e conterá os argumentos, um por cadeia. Por
convenção, argv[0] conterá o nome do programa que foi ativado, de forma que argc é pelo menos 1.

2.2. Argumentos em funções genéricas


Em C, argumentos de funções são passados por valor, ou seja, a função chamada recebe uma
cópia temporária e privativa de cada argumento, e não seu endereço. Isto significa que a função chamada
não pode afetar o argumento original da função chamadora. Dentro de uma função, cada argumento é,
essencialmente, uma variável local inicializada com o valor com o qual a função foi chamada. No
entanto, a passagem de apontadores permite emular o efeito de passagem por referência.

Programando em Linguagem ANSI C


Material Complementar – EA869 (Prof. Von Zuben – DCA/FEEC/Unicamp) 2

2.3. Bibliotecas C e arquivos-fonte com extensão “.h”


A linguagem C apresenta várias funções e constantes auxiliares definidas em bibliotecas C
(arquivos com extensão “.h”). Em anexo encontra-se uma relação, em ordem alfabética, das funções
definidas nas bibliotecas para ANSI C. Para maiores detalhes sobre estas funções, consulte-as utilizando
o comando man.
O usuário também pode definir seus próprios arquivos de cabeçalho (com extensão “.h”). Como
estes arquivos são geralmente compartilhados por vários arquivos-fonte (com extensão “.c”), é
recomendado incluir apenas:
• definições de macros;
• declarações de estruturas, uniões e enumerações
• declarações utilizando typedef;
• declaração de funções externas;
• declaração de variáveis globais.
Com isso, é recomendado deixar de fora apenas as definições e declarações privativas a cada
arquivo-fonte.

2.4. Alocação dinâmica de memória


Para toda chamada a malloc deve corresponder uma chamada a free, mesmo em funções
com variáveis locais, pois o que é desalocado ao término da execução da função é o apontador (variável
local) e não o que é apontado por ele (alocado dinamicamente com malloc). Além disso, a
implementação de malloc/free não retorna necessariamente a memória alocada e depois desalocada
ao sistema operacional, mas simplesmente a torna disponível a futuras chamadas a malloc dentro do
mesmo programa.
Tomando um apontador para inteiros como exemplo, o comando
p = (int *)calloc(m*n,sizeof(int))

é essencialmente equivalente a
p = (int *)malloc(m*n*sizeof(int));
memset(p,0,m*n*sizeof(int));

Há ainda a função realloc, que permite alterar a dimensão de uma área pré-alocada. Esta
função recebe dois argumentos: o apontador para a área que havia sido previamente alocada e a nova
dimensão para esta área (em bytes). Quando malloc, calloc e realloc não conseguem alocar o
espaço requisitado, o valor retornado pela função é o apontador nulo, que aponta para um endereço
especial que não pode ser atribuído a nenhuma variável.

2.5. Alocação dinâmica de arranjos multidimensionais


A declaração de arranjos multidimensionais pode se dar via definição direta das dimensões ou
então utilizando conjuntamente apontadores e alocação dinâmica de memória. A seguir, é apresentado o
caso de arranjos bidimensionais, que pode ser devidamente adaptado para mais dimensões.
Nos exemplos apresentados não está incluído um teste para verificar se a alocação foi bem
sucedida, o que é sempre recomendável.

2.5.1. Alocação estática


Caso já se conheça as dimensões do arranjo bidimensional e desde que seus valores sejam
aceitos pelo compilador (valores muito elevados para as dimensões podem provocar problemas de
alocação estática), a declaração pode assumir a forma (tomando arranjos do tipo inteiro):
int arranjo[NLIN][NCOL];

sendo que o acesso ao elemento (i,j) do arranjo se dá utilizando a indexação arranjo[i][j], com i e j
inteiros tais que 0 ≤ i < NLIN e 0 ≤ j < NCOL.
Como uma alternativa, é possível simular um arranjo bidimensional via um arranjo
unidimensional na forma:
int arranjo[NLIN*NCOL];

sendo que o acesso ao elemento (i,j) do arranjo se dá utilizando a indexação arranjo[i*NCOL+j],


com i e j inteiros tais que 0 ≤ i < NLIN e 0 ≤ j < NCOL.

Programando em Linguagem ANSI C


Material Complementar – EA869 (Prof. Von Zuben – DCA/FEEC/Unicamp) 3

2.5.2. Alocação dinâmica


No caso de simulação de um arranjo bidimensional utilizando-se um arranjo unidimensional, a
alocação dinâmica é mais simples:
#include <stdlib.h>
int nlin,ncol;
int *arranjo;
/* definicao on-line do numero de linhas e colunas */
arranjo = (int *)malloc(nlin*ncol*sizeof(int));

Tratando-se diretamente o arranjo como bidimensional, existem duas possibilidades:


• alocar dinamicamente apenas o número de linhas:
#include <stdlib.h>
int nlin;
int *arranjo[NCOL];
/* definicao on-line do numero de linhas */
int (*arranjo)[NCOL] = (int (*)[NCOL])malloc(nlin*sizeof(*arranjo));

• alocar dinamicamente linhas e colunas:


#include <stdlib.h>
int nlin,ncol;
int **arranjo;
/* definicao on-line do numero de linhas e colunas */
arranjo = (int **)malloc(nlin*sizeof(int *));
for(i=0;i<nlin,i++)
arranjo[i] = (int *)malloc(ncol*sizeof(int));

Esta última forma é a mais recomendada, mesmo que se conheça antecipadamente o número de
linhas e colunas. No entanto, ela não garante alocação de posições contíguas de memória. Para obter
alocação de posições contíguas, sugere-se o seguinte código:
#include <stdlib.h>
int nlin,ncol;
int **arranjo;
/* definicao on-line do numero de linhas e colunas */
arranjo = (int **)malloc(nlin*sizeof(int *));
arranjo[0] = (int *)malloc(nlin*ncol*sizeof(int));
for(i=0;i<nlin,i++)
arranjo[i] = arranjo[0] + i*ncol;

2.5.3. Passagem de um arranjo bidimensional a uma função


Caso se conheça o número de colunas, a declaração da função deve ser:
tipo1 f(a) OU tipo1 f(a)
tipo2 a[][NCOL]; {} tipo2 (*a)[NCOL]; {}

Para número de linhas e colunas definidos em tempo de execução, é fundamental que se garanta
alocação contígua de memória, permitindo utilizar a seguinte declaração de função:
tipo1 f(a,nlin,ncol)
tipo2 **a;
int nlin;
int ncol; {}

2.6. Funções que retornam múltiplos valores


São basicamente três as formas de uma função retornar múltiplos valores:
• utilização de variáveis globais;
• passagem de apontadores como argumentos da função;
• retorno de um apontador para uma estrutura ou arranjo (que não seja uma variável local da função que
vai retorná-la/o) contendo os valores desejados.

2.7. Outros tópicos


• diretivas para compilação condicional;
#if exp_constante OU #ifdef identificador
...
#else (opcional)
...
#endif

Programando em Linguagem ANSI C


Material Complementar – EA869 (Prof. Von Zuben – DCA/FEEC/Unicamp) 4

outros:
#ifndef
#elif

• distinção entre Standard (Kernighan&Ritchie) C e ANSI C: declaração de protótipos;

K&R ANSI
int f(a,b) int main()
int a; {
int b; int f(int,int);
{} double c,d;
int e;
int main() e = f(c,d);
{ }
double c,d;
int e; int f(int a, int b)
e = f(c,d); {}
}

3. Você sabia?
• na expressão f()+g()*h(), o produto vai ser realizado antes da soma, mas não é possível antecipar
qual vai ser a ordem de execução das três funções. Por outro lado, em expressões envolvendo os
operadores <&&>, <||>, <?:> e <,>, como em
while((c=getchar()) != EOF && c !='\n'){} /* while(f() && g()){} */

a execução da esquerda para a direita é garantida.


• expressões como if(p) ou if(!p) são interpretadas pelo compilador respectivamente como
if(p!=0) e if(p==0). Se p for um apontador, compara-se p com NULL (apontador nulo).
• a expressão a[5] (sexto elemento de um arranjo a) é interpretada pelo compilador como
*((a)+(5)). Isto significa que a[5] é idêntico a 5["a"].
• a função scanf usa %f para float e %lf para double, enquanto que a função printf usa %f para
float e double.
• em expressões que envolvem comparações entre uma variável e uma constante, é comum utilizar, por
exemplo, if(0==x) em lugar de if(x==0) como uma forma de permitir que o compilador detecte
os casos em que se empregou por engano o operador de atribuição (=) quando se desejava comparar
duas expressões (==).

4. Atividades práticas
1) Quais são as saídas geradas pelos seguintes programas compilados com cc? O que elas representam?

(a) proga.c (tipos de dados)

#include <stdio.h>

#define IMPRIME(formato, x) printf(#x " = " #formato "\n", x)

int inteiro = 5;
char caractere = '5';
char *cadeia = "5";

int main()
{
IMPRIME(%d,cadeia); IMPRIME(%d,caractere); IMPRIME(%d,inteiro);
IMPRIME(%s,cadeia); IMPRIME(%c,caractere);
inteiro = 53; IMPRIME(%c,inteiro);
IMPRIME(%d,('5'>5));
{
int ax = 8;
int sx = -8;
unsigned int ux = -8;
IMPRIME(%o,ax); IMPRIME(%o,sx); IMPRIME(%o,ux);
sx = sx>>3; IMPRIME(%o, sx); ux = ux>>6; IMPRIME(%o, ux);
IMPRIME(%d, sx); IMPRIME(%d, ux);
}
return(1);
}

Programando em Linguagem ANSI C


Material Complementar – EA869 (Prof. Von Zuben – DCA/FEEC/Unicamp) 5

(b) progb.c (operadores aritméticos)

#include <stdio.h>

#define IMPRIME(int) printf("%d\n", int)

int main()
{
int x,y,z;
x = 2; y = 1; z = 0;
x = x && y || z; IMPRIME(x);
IMPRIME(x || !y && z);
x = y = 1;
z = x ++ - 1; IMPRIME(x); IMPRIME(z);
z += -x ++ + ++ y; IMPRIME(x); IMPRIME(z);
return(1);
}

(c) progc.c (operadores lógicos)

#include <stdio.h>

#define IMPRIME(int) printf("%d\n", int)

int main()
{
int x,y,z;
x = 03; y = 02; z = 01;
IMPRIME(x|y&z); IMPRIME(x|y&~z); IMPRIME(x^y&~z); IMPRIME(x&y&&z);
x = 1; y = -1;
IMPRIME(!x | x); IMPRIME(~x | x); IMPRIME(x ^ x);
x <<= 3; IMPRIME(x); y <<= 3; IMPRIME(y); y >>= 3; IMPRIME(y);
return(1);
}

(d) progd.c (vetores multidimensionais)

#include <stdio.h>

#define PR(formato,valor) printf(#valor " = " #formato "\t", (valor))


#define NL putchar('\n');
#define IMPRIME1(f,x1) PR(f,x1), NL
#define IMPRIME2(f,x1,x2) PR(f,x1), IMPRIME1(f,x2)
#define IMPRIME3(f,x1,x2,x3) PR(f,x1), IMPRIME2(f,x2,x3)

int a[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int *pa[3] = {
a[0], a[1], a[2]
};
int *p = a[0];

int main()
{
int i;
for (i=0; i<3; i++)
IMPRIME3(%d, a[i][2-i],*a[i],*(*(a+i)+i)); NL;
for (i=0; i<3; i++)
IMPRIME2(%d, *pa[i], p[i]);
return(1);
}

(e) proge.c (vetores de apontadores para estruturas)

#include <stdio.h>

#define PR(formato,valor) printf(#valor " = " #formato "\t", (valor))


#define NL putchar('\n');
#define IMPRIME1(f,x1) PR(f,x1), NL
#define IMPRIME2(f,x1,x2) PR(f,x1), IMPRIME1(f,x2)
#define IMPRIME3(f,x1,x2,x3) PR(f,x1), IMPRIME2(f,x2,x3)

Programando em Linguagem ANSI C


Material Complementar – EA869 (Prof. Von Zuben – DCA/FEEC/Unicamp) 6

struct S1{
char *s;
struct S1 *s1p;
};

int main()
{
static struct S1 a[] = {
{"abcd", a+1},
{"efgh", a+2},
{"ijkl", a}
};
struct S1 *p[3];
int i;
for (i=0; i<3; i++) p[i] = a[i].s1p;
IMPRIME3(%s, p[0]->s, (*p)->s, (**p).s);
troca(*p,a); IMPRIME3(%s, p[0]->s, (*p)->s, (*p)->s1p->s);
troca(p[0],p[0]->s1p); IMPRIME3(%s, p[0]->s, (*++p[0]).s, ++(*++(*p)->s1p).s);
return(1);
}

int troca(struct S1 *p1,struct S1 *p2)


{
char *tmp;
tmp = p1->s;
p1->s = p2->s;
p2->s = tmp;
return(1);
}

2) O programa a seguir apresenta problemas de implementação. Discrimine os problemas e proponha


uma alternativa para eliminá-los, garantindo a execução da operação desejada. As linhas de código em
que está presente a seqüência de comentário em branco (/* */) devem ser mantidas inalteradas no
código alternativo a ser gerado, podendo ser introduzido código adicional entre estas linhas.

(a) prog1.c
/* Este programa aceita o primeiro nome digitado pelo usuario
e compoe uma frase a ser apresentada na tela
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main()
{
char *nome,mens1[100],mens2;
mens2 = malloc(100); /* */
printf("Qual e' seu nome? "); /* */
scanf("%s",nome); /* */
mens1 = "Bem-vindo, ";
mens2 = " `as atividades praticas do curso EA869!\n";
printf("%s%s%s",mens1,nome,mens2); /* */
return(0);
}

3) Por que não é possível corrigir o nome do curso atribuído a p na forma:


char *p="EA868";
p[4] = '9'; /* erro! */

4) Com base na noção de escopo de variáveis em C, explique o motivo pelo qual os arranjos de
caracteres cnum1 e cnum2 recebem lixo (ftoc converte ponto flutuante para caractere). Apresente as
correções necessárias para resolver o problema.
#include <stdio.h>

main()
{
char *ftoc(double);
char *cnum1,*cnum2;
double a=13.956,b=123456789.01;
cnum1 = ftoc(a); cnum2 = ftoc(b);
printf("%s %s\n",cnum1,cnum2);
return(1);
}

Programando em Linguagem ANSI C


Material Complementar – EA869 (Prof. Von Zuben – DCA/FEEC/Unicamp) 7

char *ftoc(double a)
{
char b[100];
sprintf(b,"%f",a);
return b;
}

5) Use o comando sizeof para verificar a quantos bytes corresponde um char, int, float, double, short
int, unsigned int, long int. Verifique o valor retornado por sizeof('a') e então responda como são
representados os caracteres alfanuméricos. Com base na representação adotada para os caracteres
alfanuméricos, indique como deve ser declarado o valor de retorno (c) da função getchar em
while((c=getchar())!='z'){}

6) Em uma expressão condicional para verificar se um arranjo de caracteres abc previamente definido
casa com uma determinada expressão (seqüência de caracteres), deve-se utilizar
strcmp(abc,"expressao")==0 e nunca abc == "expressao". Explique o motivo de não ser
possível utilizar abc == "expressao".

7) Sabendo-se que a função rand(), definida em <stdlib.h>, retorna um valor inteiro aleatório no
intervalo (0,RAND_MAX), escreva um programa que receba zero, um ou dois argumentos (valores
inteiros) e retorne um número inteiro aleatório. De acordo com o número de argumentos (nargs), o
programa deve retornar um número inteiro aleatório:
• no intervalo (0,RAND_MAX), se nargs=0;
• no intervalo (0,arg1) ou (arg1,0) -- arg1 pode ser negativo --, se nargs=1;
• no intervalo (arg1,arg2) ou (arg2,arg1) -- vai depender de quem é maior --, se nargs=2;
Como vai ser necessário trabalhar com escalas, é recomendado o uso de retipagem. O
programa não deve aceitar mais que dois argumentos. Como arg1 e arg2 podem ser maiores que
RAND_MAX, utilizar long int.

8) Devido à necessidade de representação binária, não é possível assumir que os resultados em ponto
flutuante sejam exatos. Isto implica na impossibilidade de verificar a igualdade de duas variáveis
declaradas como float ou double, como no trecho de programa:
double a,b;
/* bloco de comandos que define valores para a e b */
if(a == b){}

Este problema é também encontrado em qualquer outra linguagem de programação. Sendo


assim, o que você proporia como uma alternativa viável para comparação entre as variáveis a e b?
Sugestão: utilize uma variável epsilon como limiar de diferenciação entre a e b.

9) Escreva um programa que receba como argumento dois números (inteiros ou reais) intercalados por
um símbolo indicando uma operação (+, −, x ou /), retornando o resultado da operação na forma:
arg1 arg2 arg3 = resultado.

Programando em Linguagem ANSI C