Eu li que converter um ponteiro de função em um ponteiro de dados e vice-versa funciona na maioria das plataformas, mas não é garantido que funcione. Por que esse é o caso? Os dois não deveriam ser simplesmente endereços na memória principal e, portanto, ser compatíveis?
c++
c
pointers
function-pointers
gexicida
fonte
fonte
void
. A conversão de um ponteiro de função paravoid *
não deve alterar a representação. Umvoid *
valor resultante dessa conversão pode ser convertido novamente no tipo de ponteiro da função original, usando uma conversão explícita, sem perda de informações. Nota : O padrão ISO C não exige isso, mas é necessário para conformidade com POSIX.dlsym()
- observe o final da seção 'Uso de aplicativos', onde diz: Observe que a conversão de umvoid *
ponteiro para um ponteiro de função como em:fptr = (int (*)(int))dlsym(handle, "my_function");
não é definida pelo padrão ISO C. Este padrão requer que esta conversão funcione corretamente em implementações em conformidade.Respostas:
Uma arquitetura não precisa armazenar código e dados na mesma memória. Com uma arquitetura de Harvard, o código e os dados são armazenados em uma memória completamente diferente. A maioria das arquiteturas são arquiteturas de Von Neumann com código e dados na mesma memória, mas C não se limita a apenas certos tipos de arquiteturas, se possível.
fonte
CS != DS
).VirtualProtect
, o que permite marcar regiões de dados como executáveis.Alguns computadores possuem (tinham) espaços de endereço separados para código e dados. Em tal hardware, simplesmente não funciona.
O idioma foi projetado não apenas para aplicativos de desktop atuais, mas para permitir sua implementação em um grande conjunto de hardware.
Parece que o comitê de linguagem C nunca pretendeu
void*
ser um ponteiro para funcionar, eles apenas queriam um ponteiro genérico para objetos.A justificativa do C99 diz:
Nota Nada é dito sobre ponteiros para funções no último parágrafo. Eles podem ser diferentes de outros indicadores, e o comitê está ciente disso.
fonte
void *
.sizeof(void*) == sizeof( void(*)() )
isso desperdiçaria espaço no caso de ponteiros de função e ponteiros de dados terem tamanhos diferentes. Este era um caso comum nos anos 80, quando o primeiro padrão C foi escrito.Para aqueles que se lembram do MS-DOS, Windows 3.1 e anteriores, a resposta é bastante fácil. Tudo isso usado para suportar vários modelos de memória diferentes, com combinações variadas de características para indicadores de código e dados.
Por exemplo, para o modelo Compact (código pequeno, dados grandes):
e, inversamente, no modelo Médio (código grande, dados pequenos):
Nesse caso, você não tinha armazenamento separado para código e data, mas ainda não conseguiu converter entre os dois ponteiros (exceto por usar modificadores __near e __far não padronizados).
Além disso, não há garantia de que, mesmo que os ponteiros tenham o mesmo tamanho, eles apontem para a mesma coisa - no modelo de memória DOS pequeno, código e dados usados perto de ponteiros, mas apontaram para segmentos diferentes. Portanto, converter um ponteiro de função em um ponteiro de dados não forneceria um ponteiro que tivesse qualquer relação com a função e, portanto, não havia utilidade para essa conversão.
fonte
int*
em umvoid*
fornece um ponteiro com o qual você realmente não pode fazer nada, mas ainda é útil poder realizar a conversão. (Isso ocorre porquevoid*
pode armazenar qualquer ponteiro de objeto, portanto, pode ser usado para algoritmos genéricos que não precisam saber qual o tipo que eles possuem. O mesmo poderia ser útil para ponteiros de função, se fosse permitido.)int *
paravoid *
,void *
é garantido que pelo menos aponte para o mesmo objeto que o originalint *
- portanto, isso é útil para algoritmos genéricos que acessam o objeto apontado, comoint n; memcpy(&n, src, sizeof n);
. No caso em que a conversão de um ponteiro de função para avoid *
não produz um ponteiro apontando para a função, não é útil para tais algoritmos - a única coisa que você pode fazer é converter avoid *
volta para um ponteiro de função novamente, portanto, você pode bem, basta usar umunion
contendo umvoid *
ponteiro e função.void*
que aponte para a função, suponho que seria uma má idéia para as pessoas passá-lamemcpy
. :-Pvoid
. A conversão de um ponteiro de função paravoid *
não deve alterar a representação. Umvoid *
valor resultante dessa conversão pode ser convertido novamente no tipo de ponteiro da função original, usando uma conversão explícita, sem perda de informações. Nota : O padrão ISO C não exige isso, mas é necessário para conformidade com POSIX.Os ponteiros para anular devem ser capazes de acomodar um ponteiro para qualquer tipo de dados - mas não necessariamente um ponteiro para uma função. Alguns sistemas têm requisitos diferentes para ponteiros para funções e ponteiros para dados (por exemplo, existem DSPs com endereços diferentes para dados versus código, o modelo médio no MS-DOS usou ponteiros de 32 bits para código, mas apenas ponteiros de 16 bits para dados) .
fonte
Além do que já foi dito aqui, é interessante observar o POSIX
dlsym()
:fonte
void*
.void*
ser compatível com um ponteiro de função, enquanto o POSIX exige .O C ++ 11 possui uma solução para a incompatibilidade de longa data entre C / C ++ e POSIX em relação a
dlsym()
. Pode-se usarreinterpret_cast
para converter um ponteiro de função para / de um ponteiro de dados, desde que a implementação suporte esse recurso.A partir do padrão, 5.2.10 para. 8, "a conversão de um ponteiro de função em um tipo de ponteiro de objeto ou vice-versa é suportada condicionalmente". 1.3.5 define "com suporte condicional" como uma "construção de programa que uma implementação não precisa suportar".
fonte
-Werror
). Uma solução melhor (e não UB) é recuperar um ponteiro para o objeto retornado pordlsym
(ievoid**
) e convertê-lo em um ponteiro para funcionar com o ponteiro . Ainda definido pela implementação, mas não causa mais um aviso / erro .dlsym
eGetProcAddress
compilar sem aviso.-pedantic
) que advertem. Novamente, nenhuma especulação é possível.Dependendo da arquitetura de destino, o código e os dados podem ser armazenados em áreas da memória fundamentalmente incompatíveis e fisicamente distintas.
fonte
void *
é grande o suficiente para armazenar qualquer ponteiro de dados, mas não necessariamente qualquer ponteiro de função.undefined não significa necessariamente não permitido, pode significar que o implementador do compilador tem mais liberdade para fazê-lo como quiser.
Por exemplo, pode não ser possível em algumas arquiteturas - indefinido permite que eles ainda tenham uma biblioteca 'C' em conformidade, mesmo que você não possa fazer isso.
fonte
Outra solução:
Supondo que o POSIX garanta que os ponteiros de função e dados tenham o mesmo tamanho e representação (não consigo encontrar o texto para isso, mas o exemplo do OP citado sugere que eles pelo menos pretendiam fazer esse requisito), o seguinte deve funcionar:
Isso evita violar as regras de aliasing, passando pelo
char []
representação, que é permitida para alias todos os tipos.Ainda outra abordagem:
Mas eu recomendaria a
memcpy
abordagem se você quiser absolutamente 100% de C. corretofonte
Eles podem ser diferentes tipos com diferentes requisitos de espaço. Atribuir a um pode fatiar irreversivelmente o valor do ponteiro, de modo que atribuir de volta resulta em algo diferente.
Eu acredito que eles podem ser de tipos diferentes porque o padrão não deseja limitar possíveis implementações que economizam espaço quando não é necessário ou quando o tamanho pode fazer com que a CPU precise fazer uma porcaria extra para usá-lo, etc.
fonte
A única solução verdadeiramente portátil é não usar
dlsym
para funções e, em vez disso, usardlsym
para obter um ponteiro para dados que contém ponteiros de função. Por exemplo, na sua biblioteca:e depois no seu aplicativo:
De qualquer forma, essa é uma boa prática de design e facilita o suporte ao carregamento dinâmico por meio de
dlopen
vinculação estática e todos os módulos em sistemas que não suportam a vinculação dinâmica ou onde o usuário / integrador de sistemas não deseja usar a vinculação dinâmica.fonte
foo_module
estrutura (com nomes exclusivos), você pode simplesmente criar um arquivo extra com uma matriz destruct { const char *module_name; const struct module *module_funcs; }
e uma função simples para procurar nesta tabela o módulo que deseja "carregar" e retornar o ponteiro correto, e use este no lugar dedlopen
edlsym
.Um exemplo moderno de onde os ponteiros de função podem diferir em tamanho dos ponteiros de dados: ponteiros de função de membro da classe C ++
Citado diretamente em https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/
tl; dr: Ao usar herança múltipla, um ponteiro para uma função membro pode (dependendo do compilador, versão, arquitetura, etc.) ser realmente armazenado como
que é obviamente maior que a
void *
.fonte
Na maioria das arquiteturas, os ponteiros para todos os tipos de dados normais têm a mesma representação; portanto, a conversão entre tipos de ponteiros de dados não é uma opção.
No entanto, é concebível que os ponteiros de função possam exigir uma representação diferente, talvez eles sejam maiores que outros ponteiros. Se o void * pudesse conter ponteiros de função, isso significaria que a representação do void * teria que ser do tamanho maior. E todas as transmissões de ponteiros de dados para / do void * teriam que executar essa cópia extra.
Como alguém mencionou, se você precisar disso, poderá obtê-lo usando um sindicato. Mas a maioria dos usos do void * é apenas para dados, portanto, seria oneroso aumentar todo o uso de memória apenas no caso de um ponteiro de função precisar ser armazenado.
fonte
Eu sei que isso não foi comentada desde 2012, mas eu pensei que seria útil acrescentar que eu faço saber uma arquitetura que tem muito ponteiros incompatíveis para dados e funções desde uma chamada em que o privilégio arquitetura cheques e carrega a informação extra. Nenhuma quantidade de elenco ajudará. É o moinho .
fonte