Relativamente aos tipos não-primitivos (registos, vetores e objetos) as diferenças são grandes:
A vida do programador de Java tem menos dilemas, porque não existe liberdade de escolha.
Existe um pormenor que, muitas vezes confundem os programadores de usam simultaneamente C e Java. Tratar-se da sintaxe usada para aceder ao campo de um registo. Em C, a sintaxe, v.c, pressupõe que a variável v contém um registo guardado diretamente no seu interior. Mas em Java, a mesma sintaxe, v.c, pressupõe que a variável v contém uma referência para um objeto guardado no exterior. Portanto: a mesma sintaxe, conceitos diferentes! Se desejarmos em C lidar com os registos através de apontadores, a sintaxe é diferente: v->c.
A operação de atribuição não se aplica a:
Se precisarmos de efetuar atribuições com vetores ou com ficheiros, temos de ser nós a programar essa operação.
Mas no caso das strings (um caso particular dos vetores), a função strcmp permite comparar strings.
Se precisarmos de comparar registos, vetores ou ficheiros, temos de ser nós a programar as operações.
No caso da função de leitura scanf o operador & é usado nos argumentos que sejam variáveis de tipo int, double e char mas não de tipo string.
O operador & também pode ser usado para representar o segmento final duma string, a começar na posição de índice i: &str[i]
Para obter um programa bem organizado e legível, o segredo está em identificar e definir quais as funções necessárias para escrever o programa. Cada função deve ser simples e resolver apenas um pequeno subproblema. Um bom programa é geralmente constituído por um grande número de funções pequenas.
Uma função pode ter qualquer número de argumentos, incluindo zero argumentos. Para indicar ausência de argumentos, usa-se o tipo void no cabeçalho da função, na zona dos argumentos.
Uma função pode ter zero ou um resultados. Para indicar ausência de resultado, usa-se o tipo void no cabeçalho da função, na zona do resultado.
Quando se define uma função é preciso indicar o nome da função, o tipo e nome dos argumentos e o tipo do resultado. Um exemplo:
int sum(int a, int b) {
return a + b;
}
As funções podem produzir efeitos laterais para além de calcular um resultado.
No caso extremo duma função que retorne void, ela não produz qualquer resultado e apenas efeitos laterais. Exemplo:
void outN(int n) {
while( n-- > 0 )
printf("%d\n", n);
}
Eis mais um exemplo duma função com parâmetro de saída, em que esse parâmetro é usado de forma sofisticada em diversos pontos do corpo da função - tente perceber bem o exemplo, pois ele está cheio de detalhes subtis e instrutivos:
void factorial(int i, int *r) {
if( i == 0 )
*r = 1;
else {
factorial(i-1, r);
*r *= i;
}
}
int main(void)
{
int arg = 10, res;
factorial(arg, &res); /* passa uma referencia para a variável x */
printf("fact(%d)=%d\n", arg, res);
return 0;
}
|
Outra consequência dos vetores serem simplesmente passados como um apontadores é o facto da função não saber o tamanho do vetor recebido. Por causa disso, o programador geralmente passa o tamanho do vetor num argumento suplementar. Repare que isto até é uma vantagem, pois assim a mesma função consegue processar vetores de diferentes tamanhos.
void write_values(int vector[], int n)
{
for( int i = 0 ; i < n ; i++ )
printf("vector[%d] = %d", i, vector[i]);
}
int main(void) {
int a[] = {1;2;3;4;5;6;7;8;9;10};
write_values(a, 10);
return 0;
}
|
Para chamar uma função definida mais à frente, é preciso declarar previamente o cabeçalho da função usando um chamado protótipo. Exemplo:
#include <stdio.h>
void g(double d); /* protótipo */
void f(void) {
g(1); /* chamada para a frente */
}
void g(double d) {
printf("%5.5lf\n", d);
}
int main(void) {
f();
return 0;
}
int main(void) int main() int main(int argc, char *argv[])
bool listSearch(List l, bool f(Data), Data *res)
{
for( ; l != NULL ; l = l->next )
if( f(l->data) ) {
*res = l->data;
return true;
}
return false;
}
|
bool MyF(Data d) {
return d < 0;
}
int main(void) {
Data d;
List l = ListNew(5, 1.2, 2.6, 3.7, -47.1, 5.0);
if( listSearch(l, MyF, &d) )
printf("---> %lf\n", d);
else
printf("No found\n");
return 0;
}
Mas note que a possibilidade de passar função por parâmetro é pouco poderosa, porque em C não existe aninhamento de função, mas só funções globais.
Há duas vantagens em usar este qualificador:
int main(void) {
const int i = 3; // Constante inteira
const int *pt; // Apontador para inteiro constante
int const *pt; // Apontador para inteiro constante
int *const pt; // Apontador constante para inteiro
int const *const pt; // Apontador constante para inteiro constante
int *const *pt; // Apontador para apontador constante para inteiro
}
A leitura destes tipos nem sempre é simples. Veja as regras:
Um caso particular importante: se um argumento duma função tem o atributo const (e.g. "const int a") isso significa que se trata dum parâmetro de entrada que não pode ser modificado no corpo da função.
Mas a portabilidade não se obtém automaticamente e o programador tem de se preocupar com essa questão.
O tamanho dos valores de diversos tipos variam de implementação para implementação e a norma da linguagem especifica apenas valores mínimos. Por exemplo, uma variável de tipo int precisa de ter um mínimo de 16 bits (mas tem muitas vezes 32 bits) e uma variável de tipo long precisa de ter um mínimo de 32 bits (mas tem muitas vezes 64 bits).
Nunca se deve assumir que um inteiro ou um long tem um tamanho particular. Se for preciso conhecer o tamanho, então ele deve ser obtido através da função sizeof.
Para não se ter de pensar no tamanho dos valores, por vezes usam-se os seguintes tipos de biblioteca:
Em todo o caso, sempre que o mínimo de 16 bits for suficiente, deve usar-se o tipo int, pois é normalmente o tipo inteiros mais eficiente da máquina.
O C99 introduziu um módulo com header stdint.h para ajudar nas situações em que precisarmos de inteiros com um número de bits conhecido à partida. Nesse ficheiro estão definidos os tipos int8_t, int16_t, int32_t, int64_t. Se precisarmos dum inteiro com tamanho suficiente para guardar um apontador, podemos usar o tipo intptr_t.
A arrumação dos campos num registo é dependente do compilador. Quando se enviam registos através da rede, dum programa para outro, convém implementar um mecanismo de serialização.
Os diversos bytes que constituem um inteiro podem ser guardados por duas ordens diferentes, consoante arquitetura do computador. Quando se enviam valores através da rede, convém usar um mecanismo de serialização que leve isso em conta.
Em 1979 foi criado um verificador chamado lint que passou a ser distribuído com todas as versões do Unix.
Uma versão melhorada, atualmente em uso, chama-se splint:
$ man splint
splint(1) splint(1)
NAME
splint - A tool for statically checking C programs
SYNOPSIS
splint [options]
DESCRIPTION
Splint is a tool for statically checking C programs for security vulnerabilities and com-
mon programming mistakes. With minimal effort, Splint can be used as a better lint(1).If
additional effort is invested adding annotations to programs, Splint can perform stronger
checks than can be done by any standard lint. For full documentation, please see
http://www.splint.org. This man page only covers a few of the available options.
|
O seguinte programa está errado:
char str[128] ;
void f(void)
{
char *pt = str ;
int i ;
for( i = 0 ; i < 10000 ; i++ ) // <-- Provoca estoiro
*pt++ = 'a' ;
}
void g(void) {
f() ;
}
int main()
{
g() ;
return 0 ;
}
|
$ ./a Segmentation fault (core dumped) $ gdb ./a GNU gdb 6.6-debian Copyright (C) 2006 Free Software Foundation, Inc. (gdb) run Starting program: /media/EXTERN/amd1/z/a Program received signal SIGSEGV, Segmentation fault. 0x0804835d in f () <-- Estoiro na função f (gdb) backtrace #0 0x0804835d in f () #1 0x0804837b in g () #2 0x08048390 in main () <-- Pilha de execução no momento do estoiro (gdb) |
VALGRIND(1) Release 3.4.0 VALGRIND(1)
NAME
valgrind - a suite of tools for debugging and profiling programs
SYNOPSIS
valgrind [[valgrind] [options]] [your-program] [[your-program-options]]
DESCRIPTION
Valgrind is a flexible program for debugging and profiling Linux executables. It consists
of a core, which provides a synthetic CPU in software, and a series of "tools", each of
which is a debugging or profiling tool. The architecture is modular, so that new tools can
be created easily and without disturbing the existing structure.
This manual page covers only basic usage and options. For more comprehensive information,
please see the HTML documentation on your system: /usr/share/doc/valgrind/html/index.html,
or online: http://www.valgrind.org/docs/manual/index.html.
INVOCATION
Valgrind is typically invoked as follows:
valgrind program args
|
gcc -g -O0 -o prog prog.ce depois corra o programa assim:
valgrind ./progExercício: Considere o seguinte programa em C. Este programa não tem erros sintáticos e portanto compila. No entanto tem diversos erros de lógica que os programadores fazem frequentemente.
#include <stdio.h>
#include <stdlib.h>
void unnitialised(void) {
printf("unnitialised start\n");
int k;
printf("k = %d\n", k);
printf("unnitialised end\n");
}
void outofbounds(void) {
printf("outofbounds start\n");
int i;
int *vect = malloc(10 * sizeof(int));
for( i = 0 ; i < 11 ; i++ )
vect[i] = 12345;
free(vect);
printf("outofbounds end\n");
}
void invalidfree(void) {
printf("invalidfree start\n");
int *pt = (int *)&pt;
free(pt);
printf("invalidfree end\n");
}
int main(void) {
unnitialised();
outofbounds();
invalidfree();
return 0;
}