Bacagine
(usa Arch Linux)
Enviado em 09/03/2026 - 18:34h
j4ned0bbs escreveu:
Bacagine escreveu:
Sou desenvolvedor C há 7 anos, e em todos esses anos, duas maneiras de usar ponteiros me chamaram atenção.
Os conceitos a seguir são avançados, algumas coisas que vou falar não são abordadas muito em livros ou em cursos por aí, então não se preocupe se tiver dificuldades em entender.
1) Ponteiros Genéricos (void*)
Os ponteiros genéricos (void pointers) são usados quando você não sabe qual tipo de dado vai ser usado. Imagine que você cria uma estrutura de lista, e ao invés de definir qual dado será salvo na lista, você cria uma lista com ponteiro genérico, assim ela passa a aceitar qualquer tipo de dado.
typedef struct STRUCT_GENERIC_LIST {
void* pData;
struct STRUCT_GENERIC_LIST* pstNext;
} STRUCT_GENERIC_LIST, *STRUCT_GENERIC_LIST;
2) Callbacks (ponteiros para funções)
Esse é um dos usos que eu mais vejo no dia a dia, trabalhando como desenvolvedor C na empresa onde estou.
Imagine que você está desenvolvendo uma biblioteca que tem uma função que precisa chamar outra, porém você não sabe o conteúdo dessa função que você vai chamar, você não tem essa função no seu código, a única coisa que você sabe é a assinatura que essa função tem. Nesse caso, você cria uma callback, um ponteiro para a função, assim quando um desenvolvedor usar sua biblioteca, ele irá passar a função como parâmetro para este ponteiro.
Veja um exemplo a seguir:
typedef int (*PFNCALLBACK)(void);
void vMyFunction(PFNCALLBACK pfnCallback);
1) Criamos um tipo de dado que é um ponteiro de função chamado PFNCALLBACK.
2) Criamos uma função que recebe como parâmetro uma função com a assinatura de PFNCALLBACK, ou seja, uma função que retorna um inteiro e não recebe parâmetros.
O desenvolvedor que for chamar a função vMyFunction terá que passar como parâmetro uma função que tenha essa assinatura. Exemplo:
int iHelloWorld(void) {
puts("Hello World!!!");
puts("I'm a callback :)");
return 1;
}
int main(void) {
vMyFunction(iHelloWorld);
return 0;
}
Caraca, essa ultima parte sobre Callbacks explodiu a minha cabeça! É genial.
Obrigada pela sua resposta eu gostei bastante e abriu a minha mente para os ponteiros. Só fiquei com pequenas duvidas e curiosidades:
Minha primeira duvida é o quanto um dado "Void" ocupa de bytes de memoria, por curiosidade e também por que eu queria entender como o compilador vai aceitar outros tipos de dados naquele ponteiro Void, como inteiros e caracteres sendo que eles tem tamanhos maiores e diferentes.
Outra curiosidade é, se me permite perguntar, como é trabalhar com C? Eu fiquei curiosa por que achei que empregos de programação em C fossem sumir por conta daquele borborinho todo de "linguagens vulneráveis".
Please, keep it simple!
Respondendo a sua primeira pergunta:
Um ponteiro genérico irá usar a mesma quantidade de bytes que qualquer ponteiro, no caso, 8 bytes. Para ver isso, você pode fazer o seguinte:
printf("void* tem tamanho de %ld bytes\n", sizeof(void*));
Ao manipular o ponteiro genérico, você precisa dizer qual tipo de dado ele é no momento da manipulação, você fará isso por meio de casting.
Vou dar um exemplo mais detalhado de ponteiros genéricos para você entender melhor na prática o uso, vou fazer algo simples, pois o uso disso pode ser muito mais complexo do que irei mostrar a seguir.
Imagine que temos um software com algumas estruturas, e queremos uma função para mostrar o conteúdo dessas estruturas. Porém, não queremos uma função para cada estrutura, mas sim uma única função que irá mostrar o conteúdo da estrutura passada como parâmetro, independente de qual seja a estrutura passada.
Nós faríamos o seguinte:
#include <stdio.h>
#include <string.h>
typedef enum ENUM_STRUCT_TYPE {
TYPE_PERSON,
TYPE_CAT
} ENUM_STRUCT_TYPE;
typedef struct STRUCT_PERSON {
char szName[64];
int iAge;
char szMsg[32];
} STRUCT_PERSON, *PSTRUCT_PERSON;
typedef struct STRUCT_CAT {
char szName[64];
int iAge;
char szMsg[32];
} STRUCT_CAT, *PSTRUCT_CAT;
void vPrintInfo(void* pData, ENUM_STRUCT_TYPE eStructType) {
switch ( eStructType ) {
case TYPE_PERSON: {
PSTRUCT_PERSON pstPerson = (PSTRUCT_PERSON) pData;
printf("============== PERSON ==============\n");
printf(
"Person name...: %s\n"
"Person age....: %d\n"
"Person Message: %s\n",
pstPerson->szName,
pstPerson->iAge,
pstPerson->szMsg
);
printf("====================================\n");
break;
}
case TYPE_CAT: {
PSTRUCT_CAT pstCat = (PSTRUCT_CAT) pData;
printf("============== CAT ==============\n");
printf(
"Cat name...: %s\n"
"Cat age....: %d\n"
"Cat Message: %s\n",
pstCat->szName,
pstCat->iAge,
pstCat->szMsg
);
printf("=================================\n");
break;
}
default: {
fprintf(stderr, "E: Invalid struture type!\n");
break;
}
}
}
int main(void) {
STRUCT_PERSON stPerson;
STRUCT_CAT stCat;
memset(&stPerson, 0x00, sizeof(stPerson));
memset(&stCat , 0x00, sizeof(stCat ));
snprintf(stPerson.szName, sizeof(stPerson.szName), "Gustavo Bacagine");
stPerson.iAge = 26;
snprintf(stPerson.szMsg, sizeof(stPerson.szMsg), "Hi, I'm C developer :)");
snprintf(stCat.szName, sizeof(stCat.szName), "Tigris");
stCat.iAge = 3;
snprintf(stCat.szMsg, sizeof(stCat.szMsg), "Miau");
vPrintInfo(&stPerson, TYPE_PERSON);
vPrintInfo(&stCat, TYPE_CAT);
vPrintInfo(&stCat, -1);
return 0;
}
A saída esperada seria:
============== PERSON ==============
Person name...: Gustavo Bacagine
Person age....: 26
Person Message: Hi, I'm C developer :)
====================================
============== CAT ==============
Cat name...: Tigris
Cat age....: 3
Cat Message: Miau
=================================
E: Invalid struture type!
Para seu aprendizado, gostaria de deixar como desafio que você reescreva o código acima, mas ao invés de usar ponteiro genérico, use callbacks.
Agora respondendo à sua segunda pergunta (lembrando que vou falar com base na minha experiência pessoal):
Atualmente, no Brasil, vejo dois cenários onde a linguagem C é dominante:
1) Sistemas Embarcados
2) Varejo (este é o ramo onde trabalho)
Sou desenvolvedor em uma software house que desenvolve software para supermercados, farmácias, postos de combustíveis, etc.
Eu trabalho na equipe responsável pela parte do PDV (o software que roda nos computadores onde tem o caixa do mercado ou nos self checkouts).
Esses softwares demandam extrema rapidez e baixo uso de memória. Exemplo: ao passar um item no leitor de código de barras, é necessário que o software busque as informações na base de dados e exiba todas as informações do item para o usuário do sistema em questão, às vezes em menos de um segundo. A linguagem C é o ideal para esse tipo de situação.
Muitas das empresas que desenvolvem software na área usam linguagens como C ou C++, a empresa onde trabalho utiliza apenas C, mas conheci outras empresas que usam C++.
Pode ser que existam empresas na área do varejo que usem outras linguagens, mas percebo que a linguagem C é a mais usada.
Como a empresa em que trabalho desenvolve o software há mais de vinte anos, dificilmente eles iriam migrar o software da linguagem C para uma nova linguagem.
Trabalhar com linguagem C é algo que traz muitos benefícios devido à velocidade da linguagem, porém, é necessário um cuidado maior ao desenvolver para evitar vazamento de memória, buffer overflow, etc.
Os riscos existem, e é necessário cautela em muitos casos, principalmente quando se trata de manipulação de memória.
Uma vez eu atendi um chamado onde o software morria por falha de segmentação. Após algumas horas analisando logs da aplicação e conversando com outros desenvolvedores, percebi que o problema era free em um ponteiro que não era alocado. Esse chamado foi um dos mais difíceis que já tive até hoje, pois ao olhar os logs, a aplicação não morria no ponto onde havia o free na variável, mas morria em outro local, isso é muito comum em softwares grandes quando tem vazamento de memória, às vezes o software não morre no local onde tem o problema, o que dificulta a análise do problema.
Acredito que toda linguagem de programação tem benefícios, porém junto com eles alguns problemas, mas não acredito que o trabalho como desenvolvedor C ou C++ irá sumir tão cedo, existem muitos sistemas por aí usando essas linguagens e que provavelmente não irão migrar para linguagens mais modernas.
Não acho que a solução seja abandonar a linguagem C para outra, mas creio que devemos aprender a desenvolver com mais cautela. Muitas pessoas hoje em dia gostam de aprender com linguagens mais simples, o que pode gerar desenvolvedores com pouco conhecimento e entendimento do que estão fazendo. Creio que aprender C/C++ seja algo importante, mesmo que você não vá trabalhar usando elas.