You are currently browsing Thiago Santos's articles.
Algumas vezes é possível utilizar alguns paradigmas de orientação à objetos em C. Ao se projetar uma biblioteca, podemos encapsular o conteúdo das structs usando tipos incompletos e provendo funções para manipular o conteúdo destas.
Imagine uma biblioteca que cria um “objeto” para representar uma pessoa. Esta biblioteca provê um cabeçalho person.h com funções para manipular o “objeto”.
typedef struct person person; person *person_new(char *name, int age); void person_free(person *handler); void person_print(person *handler);
Note que não existe uma descrição da struct person no cabeçalho. A única forma então de manipular esta struct é usando as funções, porque os atributos da struct não são conhecidos. Esta struct portanto caracteriza um tipo incompleto, mas apenas do ponto de vista dos usuários da biblioteca. Em sua implementação, a biblioteca conhece o conteúdo de person e consegue manipulá-los, como vemos a seguir.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "person.h"
struct person {
char *name;
int age;
};
person *person_new(char *name, int age)
{
person *new = malloc(sizeof(*new));
new->name = strdup(name);
new->age = age;
return new;
}
void person_free(person *handler)
{
free(handler->name);
free(handler);
}
void person_print(person *handler)
{
printf("%s - %d\n", handler->name, handler->age);
}
A vantagem de se encapsular os dados, forçando esta política através da técnica apresentada, é que a implementação da biblioteca pode ser alterada sem impacto em seus clientes. person_new poderia checar a validade do atributo age, retornando um ponteiro nulo caso seja passado uma idade negativa. O código seguinte ilustra um possível cliente da biblioteca, note que não há referência aos atributos de struct person.
#include <person.h>
int main(void)
{
person *mike = person_new("Mike", 21);
person_print(mike);
person_free(mike);
return 0;
}
A primeira vez que vi uma biblioteca fazendo uso extenso desta técnica foi no projeto OpenSync, que inspirou este post.
As bibliotecas compartilhadas são carregadas no início da execução de um programa. No Linux, o dynamic loader procura pelas bibliotecas em /lib e /usr/lib. Caso a biblioteca não esteja presente neste caminho, recebemos uma mensagem de erro parecida com a mensagem a seguir:
error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
Imagine um ambiente de desenvolvimento, onde estamos codificando uma biblioteca. Não queremos instalar esta biblioteca no sistema só para testá-la. Uma alternativa é configurar a variável de ambiente LD_LIBRARY_PATH, apontando para o diretório onde se encontra o binário da biblioteca. Assim o dynamic loader vai procurar pela biblioteca também neste diretório.
$ export LD_LIBRARY_PATH=/home/user/libfoo/
Uma segunda opção seria no momento em que seu programa é “linkado”, passar o caminho da biblioteca para a opção -rpath do linker. Isto coloca o caminho de busca pela biblioteca dentro da estrutura do executável (ELF). A opção -Wl do gcc serve para passar parâmetros para o linker (usando “,” no lugar de espaço) que é chamado automaticamente após a compilação.
$ gcc -shared -Wall -o libfoo.so foo.c
$ gcc -Wall -o test test.c -L/home/user/libfoo/ -lfoo
$ ./test # Erro! Não acha a biblioteca libfoo.so
$ gcc -Wall -Wl,-rpath,/home/user/libfoo/ -o test test.c \ -L/home/user/libfoo/ -lfoo
$ ./test # Funciona!
Um mesmo aplicativo pode se comportar de forma diferente dependendo da forma como é invocado. No exemplo a seguir, o mesmo programa é usado para calcular a raiz quadrada e potência de dois de um dado argumento.
#include <libgen.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
double res;
char *program = basename(argv[0]);
if (argc != 2)
return 1;
if (!strcmp(program, "sqrt")) {
res = sqrt(atof(argv[1]));
printf("%f\n", res);
return 0;
}
if (!strcmp(program, "pow2")) {
res = pow(atof(argv[1]), 2);
printf("%f\n", res);
return 0;
}
printf("Invalid program: %s\n", program);
return 1;
}
O truque consiste em analisar o primeiro argumento que corresponde ao caminho do binário do programa. No exemplo, decidimos qual ação tomar comparando o nome do programa com “sqrt” e “pow2″. Para não fazer múltiplas cópias do binário gerado, fazemos links simbólicos.
$ gcc -Wall -lm -o test test.c $ ln -s test sqrt $ ln -s test pow2 $ ./test 5 Invalid program: test $ ./pow2 5 25.000000 $ ./sqrt 5 2.236068
A técnica é extensamente utilizada no BusyBox e pode facilmente ser implementada em qualquer outra linguagem de programação.
Em C não existe um tipo nativo booleano, temos 0 como falso e tudo diferente de 0 é verdadeiro. Em C++ existe o tipo booleano, onde verdadeiro e falso correspondem respectivamente a 0 e 1.
Uma forma de fazer um “cast” em C para um tipo booleano compatível com C++ seria usando a dupla negação, como no exemplo a seguir.
int value1 = 0;
int value2 = 1;
int value3 = 50;
int value4 = -1;
printf("value1 = %d\n", !!value1); // Imprime value1 = 0
printf("value2 = %d\n", !!value2); // Imprime value2 = 1
printf("value3 = %d\n", !!value3); // Imprime value3 = 1
printf("value4 = %d\n", !!value4); // Imprime value4 = 1
Classes que implementam o método __len__, quando testadas com if, este método é invocado. Portanto, para uma lista vazia, o teste falha.
x = [];
if x:
print "OK" # Nao imprime nada
Para mudar este comportamento, basta reimplementar o método __nonzero__.
class MyList(list):
def __nonzero__(self):
return True
x = MyList();
if x:
print "OK" # Imprime OK
No Linux é muito comum um daemon, durante sua execução, criar um arquivo .pid dentro de /var/run. Dentro do arquivo syslogd.pid, por exemplo, contém o PID da instância do syslogd em execução. Usa-se este mecanismo para impedir que duas instâncias do mesmo processo rodem simultaneamente e conflitem na obtenção de recursos.
Porém, a simples existência do arquivo .pid não garante que o processo esteja em execução. Ele pode ter sido fechado de uma forma inesperada e não o apagou. Então, temos que ler o conteúdo do arquivo .pid e certificar que o processo com aquele número de PID está em execução.
A forma mais fácil de fazer isso é enviando o sinal 0 para o processo. O sinal 0 é especial e não é de fato enviado, caso contrário o processo destinatário poderia ser fechado se não tratasse o sinal, mas o retorno da função indica se um sinal real seria enviado com sucesso. O código a seguir exemplifica como aplicar a técnica em C:
int main(int argc, char **argv)
{
unsigned int pid;
if (argc != 2) {
printf("Uso: %s PID\n", argv[0]);
return 1;
}
pid = (unsigned int) atoi(argv[1]);
if (!kill(pid, 0))
printf("%d esta em execucao.\n", pid);
else
printf("%d nao esta em execucao.\n", pid);
return 0;
}
A mesma técnica pode ser aplicada no terminal:
$ kill -0 123 $ echo $? # Imprime 0 se o PID 123 existir, 1 caso contrario
Note que você precisa ter permissão para enviar um sinal para um determinado processo. Para um usuário sem privilégios especiais, a dica só vai funcionar para verificar a existência de processos iniciados pelo próprio usuário.
Um recurso bastante útil do GCC (apenas) são vetores de tamanho zero. O uso é permitido apenas como último elemento de uma estrutura de dados.
struct pessoa {
int idade;
char nome[0];
};
printf("%d\n", sizeof(struct pessoa)); // Imprime "4"
Note que a estrutura tem o mesmo tamanho de um inteiro (na minha máquina). Então podemos concluir que o vetor de tamanho zero não ocupa espaço, ele apenas serve como um identificador para a primeira posição de memória após a estrutura. Isto nos permite fazer algo assim:
struct pessoa * cria_pessoa(int idade, char* nome)
{
int tamanho = sizeof(struct pessoa) + strlen(nome) + 1;
struct pessoa *novo = malloc(tamanho);
novo->idade = idade;
strcpy(novo->nome, nome);
return novo;
}
int main(void)
{
struct pessoa *teste = cria_pessoa(18, "Jose");
/* Imprime "Jose tem 18 anos." */
printf("%s tem %d anos.\n", teste->nome, teste->idade);
free(teste);
return 0;
}
Note que conseguimos alocar toda a memória para a estrutura em apenas uma operação, o que não seria possível caso “nome” fosse um ponteiro, o que nos custaria mais uma chamada de malloc. Da mesma forma a desalocação de toda a estrutura foi feita usando um comando.
Esta técnica permite maior flexibilidade e um melhor aproveitamento de memória do que construir a estrutura com o atributo nome sendo “char nome[200]” (tamanho fixo) supondo que um nome nunca seria maior que 200 caracteres.
A forma tradicional de um programa ir para background é fazendo um fork(), terminar o processo pai e criar uma nova sessão com setsid(). Outra forma mais simples, que de fato encapsula tudo isso e mais um pouco, é apenas usar a função daemon, como o Ademar sugeriu neste comentário.
#include <stdio.h>
#include <unistd.h>
int main(void)
{
if (daemon(0, 0)) {
printf("Erro indo para background.\n");
return 1;
}
printf("Esta mensagem nao deve ser impressa.\n");
sleep(10); // Durma por dez segundos antes de sair
return 0;
}
Nada será impresso porque ao passar zero para o segundo parâmetro da função, faz com a saída padrão seja redirecionada para /dev/null, assim como o Cláudio ensinou (e agora vi que dupliquei a dica dele).
Uma vez bem sucedido o comando, nosso processo estará rodando em background desconectado do terminal que o invocou. Você poderá observar que mesmo após o retorno instantâneo do terminal, o nosso programa ainda aparecerá na lista de processos por 10 segundos.







Comentários Recentes