A primeira vez que tomei conhecimento da existência da possibilidade de escrever em OO em C foi por meio dessa palestra:

Vale muito a pena estudar o slide dele!
Fiquei muito empolgado com a ideia de poder entender orientação a objetos explorando recursos da linguagem C. Como sou um curioso dessa linguagem, deixo aqui neste artigo algumas anotações sobre esse tema.
Contextualizando
As várias linguagens de programação existentes podem ser classificadas de acordo com seus paradigmas. Podemos listar alguns dos paradigmas como:
- Estruturado.
- Procedural.
- Orientado a Objetos.
- Orientado a Eventos.
- Funcional.
- Declarativo.
- Lógico.
- Paralela.
Um paradigma irá determinar a visão que o programador terá do código, ou seja, determinará como o programador irá pensar o código de uma aplicação.
Muitas linguagens de programação são multi-paradigma, suportando nativamente um ou mais paradigmas. Como é o caso da linguagem C que suporta nativamente os paradigmas procedural e estruturado.
Um ponto interessante a se saber é que é possível emular um paradigma sobre uma linguagem que não a suporta nativamente. Por exemplo: é possível emular programação funcional ou orientada a objetos sobre a linguagem C.
É claro que essa emulação não terá a qualidade esperada de uma linguagem que a suporta nativamente. Então por que alguém se daria o trabalho de fazer uma gambiarra dessas? Vamos pontuar algumas possíveis justificativas:
-
o programador pode apenas estar tentando se tornar mais íntimo de um paradigma específico, tentando entender a sua implementação.
-
um paradigma mais elaborado (como o OO) poderá ser emulado sobre uma linguagem mais simples por razões de performance (como por exemplo em sistemas embarcados usando C ao invés de C++).
-
a reprodução de algumas características de um determinado paradigma sobre outra linguagem que não a suporta pode ser útil para tornar o código mais manutenível (algumas bibliotecas em C, por exemplo, se beneficiam de características de OO para tornar o código manutenível, neste outro caso o autor utiliza OO em arquivos BAT do Windows, assim como outros exemplos, como OO em Shell Script).
Orientação a Objetos e C
Vamos discutir quais recursos da linguagem C podemos utilizar para emular a orientação a objetos. Existem alguns projetos open source que se beneficiam dessa emulação como design pattern:
- htop: visualizador de processos muito usado.
- GObject: usada pela biblioteca GTK+, a base de ambientes gráficos GNOME, Cinnamon, MATE, XFCE, e LXDE.
- Alguns padrões do kernel Linux.
Tem um livro onde o autor implementa alguns padrões de projeto da “Gangue dos 4” (originalmente pensados em C++) em C: https://leanpub.com/patternsinc.
O paradigma de orientação a objetos se sustenta sobre três pilares:
- Encapsulamento.
- Herança.
- Polimorfismo.
Basicamente, a ideia é emular esses pilares usando recursos da linguagem C como:
- structs opacas e ponteiros opacos como estruturas genéricas no código principal, e structs e ponteiros válidos somente no escopo de arquivo (válidos somente dentro de arquivos .c isolados do código principal) para emular encapsulamento de dados.
- structs aninhadas para emular herança.
- e ponteiros de função para emular polimorfismo.
Então, em resumo, uma “classe” em C será um arquivo .c dedicado a declarar uma struct que aglutinará “atributos privados” (membros da struct acessíveis somente no escopo do arquivo), “métodos” (ponteiros para funções para acesso aos membros da struct), “método construtor” (função responsável por alocar memória na heap e popular alguns membros da struct), e outras funções e variáveis internas do “objeto”.
Garbage Collector em C
No processo de compilação usaremos -lgc, no arquivo connection.c incluiremos o gc.h, e usaremos um malloc diferente chamado GC_malloc().
A libgc foi usada aqui para implementar um Garbage Collector (isso mesmo: garbage collector em C), com ela não será necessário usar free() para destruir nosso “objeto” instanciado.
Existem vários algoritmos diferentes de garbage collector (o Java, por exemplo, possui suporte a várias implementações de GC). A libgc (também chamada de Boehm-Demers-Weiser Garbage Collector) faz uso do tradicional Mark-and-Sweep.
Tá bom, vamos ver como fica
Para não ficar confuso, vamos ao seguinte código de exemplo de um cliente de rede. Mas antes duas coisas são importantes frisar:
- o arquivo main.c não conhece o conteúdo do objeto connection (connection no escopo desse arquivo é uma struct opaca). Os acessos aos “métodos” do objeto ocorre através de artimética de ponteiros.
- todo o arquivo connection.c seria a implementação da “classe” (não só a struct). E observe que a struct connection possui membros vistos apenas no escopo deste arquivo. Desta forma, a “classe” Connection poderá ser modificada sem o conhecimento de quem a chama (abstração).
Saída da execução

Código
Compila com: gcc main.c connection.c -o main -lgc
// main.c: Exemplo de C Orientado a Objeto
#include <stdio.h>
#include <stdlib.h>
#include "connection.h"
int main(int argc, char *argv[]) {
Connection *connection;
Data payload;
connection = new_Connection(); // implementa uma espécie de "construtor"
payload = (Data) "GET / HTTP/1.1";
((void (*)(Connection *, char *)) *((long int *) connection+0) ) (connection, "127.0.0.1"); // chama método setServer
((void (*)(Connection *, int)) *((long int *) connection+1) ) (connection, 80); // chama método setPort
((void (*)(Connection *, int)) *((long int *) connection+2) ) (connection, 6); // chama método setProtocol
((void (*)(Connection *, Data)) *((long int *) connection+3) ) (connection, payload); // chama método setData
((void (*)(Connection *)) *((long int *) connection+4) ) (connection); // chama método startConnection
exit(0);
}
// connection.h: definição de dados abstratos
#ifndef CONNECTION_H__
#define CONNECTION_H__
#define TCP 6
#define UDP 17
typedef struct _connection Connection;
typedef void* Data;
Connection* new_Connection(void);
#endif
// connection.c: Exemplo de implementação da "classe" connection
#include <stdio.h>
#include <stdlib.h>
#include <gc.h>
#include "connection.h"
typedef void (*fpSetServer)(Connection*, char *);
typedef void (*fpSetPort)(Connection*, int);
typedef void (*fpSetProtocol)(Connection*, int);
typedef void (*fpSetData)(Connection*, Data *);
typedef void (*fpStartConnection)(Connection*);
struct _connection {
fpSetServer setServer;
fpSetPort setPort;
fpSetProtocol setProtocol;
fpSetData setData;
fpStartConnection startConnection;
char * serverIPv4;
unsigned short int serverPort;
short int serverProtocol;
Data data;
};
void setServer(Connection* self, char *serverIPv4) {
self->serverIPv4 = serverIPv4;
}
void setPort(Connection* self, int serverPort) {
self->serverPort = serverPort;
}
void setProtocol(Connection* self, int serverProtocol) {
self->serverProtocol = serverProtocol;
}
void setData(Connection* self, Data *data) {
self->data = data;
}
void startConnection(Connection* self) {
printf(" Server: %s\n", self->serverIPv4);
printf(" Port: %d\n", self->serverPort);
printf(" Protocol: %s\n", self->serverProtocol == 6? "TCP" : "UDP");
printf(" Data: \"%s\"\n", (char *) self->data);
}
Connection* new_Connection(void) {
Connection *obj;
obj = (Connection *) GC_malloc(sizeof(Connection));
obj->setServer = setServer;
obj->setPort = setPort;
obj->setProtocol = setProtocol;
obj->setData = setData;
obj->startConnection = startConnection;
return obj;
}
Materiais interessantes
Além da já citada palestra do Hisham Muhammad, alguns outros materiais interessantes sobre esse assunto:
- Como programar em C Orientado a Objetos
- C Escondendo o conteúdo de structs com tipos incompletos
- Object-Oriented Techniques, Capítulo 8, livro Understanding and Using C Pointers
- A Fat Pointer Library