Existe alguma maneira de determinar (programaticamente, é claro) se um determinado ponteiro é "válido"? Verificar se há NULL é fácil, mas e coisas como 0x00001234? Ao tentar desreferenciar este tipo de ponteiro, ocorre uma exceção / falha.
É preferível um método de plataforma cruzada, mas específico de plataforma (para Windows e Linux) também está ok.
Atualização para esclarecimento: O problema não é com ponteiros obsoletos / liberados / não inicializados; em vez disso, estou implementando uma API que recebe ponteiros do chamador (como um ponteiro para uma string, um identificador de arquivo, etc.). O chamador pode enviar (propositalmente ou por engano) um valor inválido como o ponteiro. Como posso evitar uma falha?
Respostas:
Você não pode fazer essa verificação. Simplesmente não há como verificar se um ponteiro é "válido". Você tem que confiar que, quando as pessoas usam uma função que recebe um ponteiro, essas pessoas sabem o que estão fazendo. Se eles passarem a você 0x4211 como um valor de ponteiro, você deve confiar que ele aponta para o endereço 0x4211. E se eles "acidentalmente" atingirem um objeto, mesmo que você use alguma função assustadora do sistema operacional (IsValidPtr ou qualquer outra), você ainda escorregará para um bug e não falhará rapidamente.
Comece a usar ponteiros nulos para sinalizar esse tipo de coisa e diga ao usuário de sua biblioteca que ele não deve usar ponteiros se eles tendem a passar ponteiros inválidos acidentalmente, sério :)
fonte
Aqui estão três maneiras fáceis para um programa C no Linux se tornar introspectivo sobre o status da memória na qual está sendo executado e por que a pergunta tem respostas sofisticadas apropriadas em alguns contextos.
No Microsoft Windows, há a função QueryWorkingSetEx documentada na API de status do processo (também na API NUMA). Como corolário da programação sofisticada da API NUMA, essa função também permitirá que você faça um simples trabalho de "teste de validade (C / C ++)", como tal, é improvável que seja descontinuado por pelo menos 15 anos.
fonte
Prevenir um travamento causado pelo chamador enviando um ponteiro inválido é uma boa maneira de criar bugs silenciosos que são difíceis de encontrar.
Não é melhor para o programador que usa sua API obter uma mensagem clara de que seu código é falso, travando-o em vez de ocultá-lo?
fonte
No Win32 / 64, existe uma maneira de fazer isso. Tente ler o ponteiro e capturar a execução SEH resultante que será lançada em caso de falha. Se não lançar, é um ponteiro válido.
O problema com esse método é que ele apenas retorna se você pode ou não ler os dados do ponteiro. Não oferece nenhuma garantia sobre a segurança de tipo ou qualquer número de outras invariantes. Em geral, esse método serve apenas para dizer "sim, posso ler aquele lugar específico na memória em um momento que já passou".
Resumindo, não faça isso;)
Raymond Chen tem uma postagem no blog sobre este assunto: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
fonte
AFAIK não há como. Você deve tentar evitar essa situação, sempre definindo os ponteiros para NULL após liberar memória.
fonte
Dê uma olhada nesta e nesta questão. Também dê uma olhada nas dicas inteligentes .
fonte
Com relação à resposta um pouco acima neste tópico:
Meu conselho é ficar longe deles, alguém já postou este aqui: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
Outra postagem sobre o mesmo tópico e do mesmo autor (eu acho) é esta: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx ("IsBadXxxPtr deveria realmente ser chamado CrashProgramRandomly ").
Se os usuários de sua API enviarem dados inválidos, deixe-o travar. Se o problema for que os dados transmitidos não são usados até mais tarde (e isso torna mais difícil encontrar a causa), adicione um modo de depuração em que as strings etc. são registradas na entrada. Se forem ruins, será óbvio (e provavelmente falhará). Se estiver acontecendo com frequência, pode valer a pena tirar a API do processo e deixá-los travar o processo da API em vez do processo principal.
fonte
Em primeiro lugar, não vejo sentido em tentar se proteger do chamador que tenta causar uma falha deliberadamente. Eles poderiam fazer isso facilmente tentando acessar por meio de um ponteiro inválido. Existem muitas outras maneiras - elas podem apenas sobrescrever sua memória ou a pilha. Se você precisa se proteger contra esse tipo de coisa, você precisa estar executando em um processo separado usando soquetes ou algum outro IPC para comunicação.
Escrevemos muitos softwares que permitem que parceiros / clientes / usuários estendam a funcionalidade. Inevitavelmente, qualquer bug é relatado para nós primeiro, por isso é útil ser capaz de mostrar facilmente que o problema está no código do plug-in. Além disso, existem questões de segurança e alguns usuários são mais confiáveis do que outros.
Usamos vários métodos diferentes, dependendo dos requisitos de desempenho / taxa de transferência e confiabilidade. Dos mais preferidos:
processos separados usando soquetes (geralmente passando dados como texto).
processos separados usando memória compartilhada (se grandes quantidades de dados passarem).
o mesmo processo separa threads por meio da fila de mensagens (se houver mensagens curtas frequentes).
mesmo processo, encadeamentos separados, todos os dados transmitidos alocados de um pool de memória.
mesmo processo via chamada de procedimento direta - todos os dados passados alocados de um pool de memória.
Tentamos nunca recorrer ao que você está tentando fazer ao lidar com software de terceiros - especialmente quando recebemos os plug-ins / biblioteca como binários em vez de código-fonte.
O uso de um pool de memória é bastante fácil na maioria das circunstâncias e não precisa ser ineficiente. Se VOCÊ alocar os dados em primeiro lugar, será trivial verificar os ponteiros com os valores alocados. Você também pode armazenar o comprimento alocado e adicionar valores "mágicos" antes e depois dos dados para verificar o tipo de dados válido e saturações de dados.
fonte
Tenho muita simpatia por sua pergunta, pois também estou em uma posição quase idêntica. Eu aprecio o que muitas das respostas estão dizendo, e elas estão corretas - a rotina que fornece o ponteiro deve fornecer um ponteiro válido. No meu caso, é quase inconcebível que eles pudessem ter corrompido o ponteiro - mas se eles tivessem conseguido, seria o MEU software que travaria e eu que ficaria com a culpa :-(
Minha exigência não é que eu continue após uma falha de segmentação - isso seria perigoso - eu só quero relatar o que aconteceu ao cliente antes de encerrar, para que ele possa corrigir o código em vez de me culpar!
É assim que descobri fazer isso (no Windows): http://www.cplusplus.com/reference/clibrary/csignal/signal/
Para dar uma sinopse:
Agora, isso parece se comportar como eu esperava (ele imprime a mensagem de erro e, em seguida, encerra o programa) - mas se alguém detectar uma falha, por favor me avise!
fonte
exit()
, ele contorna RAII e, portanto, pode causar vazamento de recursos.exit()
e meu alarme portátil C ++ começou a tocar. Deve estar tudo bem nesta situação específica do Linux, onde seu programa sairia de qualquer maneira, desculpe pelo barulho.man 2 signal
no Linux tem um parágrafo explicando o porquê.No Unix, você deve ser capaz de utilizar um syscall do kernel que verifica o ponteiro e retorna EFAULT, como:
retornando:
Provavelmente existe um syscall melhor para usar do que open () [talvez access], uma vez que há uma chance de que isso leve ao codepath de criação de arquivo real e a um subsequente requisito de fechamento.
fonte
Não há provisões em C ++ para testar a validade de um ponteiro como um caso geral. Obviamente, pode-se presumir que NULL (0x00000000) é ruim, e vários compiladores e bibliotecas gostam de usar "valores especiais" aqui e ali para tornar a depuração mais fácil (por exemplo, se eu vir um ponteiro aparecer como 0xCECECECE no visual studio, eu sei Eu fiz algo errado), mas a verdade é que, como um ponteiro é apenas um índice na memória, é quase impossível dizer apenas olhando para o ponteiro se é o índice "certo".
Existem vários truques que você pode fazer com dynamic_cast e RTTI para garantir que o objeto apontado seja do tipo que você deseja, mas todos eles requerem que você esteja apontando para algo válido em primeiro lugar.
Se você quiser garantir que seu programa possa detectar ponteiros "inválidos", meu conselho é este: Defina cada ponteiro que você declara como NULL ou um endereço válido imediatamente após a criação e defina-o como NULL imediatamente após liberar a memória para a qual ele aponta. Se você for diligente quanto a essa prática, verificar se há NULL é tudo de que você precisa.
fonte
Não existe uma maneira portátil de fazer isso, e fazer isso para plataformas específicas pode ser algo entre difícil e impossível. Em qualquer caso, você nunca deve escrever código que dependa dessa verificação - não deixe que os ponteiros assumam valores inválidos em primeiro lugar.
fonte
Definir o ponteiro como NULL antes e depois de usar é uma boa técnica. Isso é fácil de fazer em C ++ se você gerencia ponteiros dentro de uma classe, por exemplo (uma string):
fonte
Não é uma política muito boa aceitar ponteiros arbitrários como parâmetros de entrada em uma API pública. É melhor ter tipos de "dados simples" como um inteiro, uma string ou uma estrutura (quero dizer uma estrutura clássica com dados simples dentro, é claro; oficialmente, qualquer coisa pode ser uma estrutura).
Por quê? Bem, porque, como outros dizem, não existe uma maneira padrão de saber se você recebeu um indicador válido ou que aponta para o lixo.
Mas às vezes você não tem escolha - sua API deve aceitar um ponteiro.
Nestes casos, é dever do chamador passar um bom ponteiro. NULL pode ser aceito como um valor, mas não um ponteiro para lixo.
Você pode verificar novamente de alguma forma? Bem, o que eu fiz em um caso como esse foi definir um invariante para o tipo para o qual o ponteiro aponta e chamá-lo quando você o obtiver (no modo de depuração). Pelo menos se o invariante falhar (ou travar), você sabe que recebeu um valor incorreto.
fonte
Como já foi dito, você não pode detectar com segurança um ponteiro inválido. Considere algumas das formas que um ponteiro inválido pode assumir:
Você pode ter um ponteiro nulo. Isso você pode facilmente verificar e fazer algo a respeito.
Você pode ter um ponteiro para algum lugar fora da memória válida. O que constitui a memória válida varia dependendo de como o ambiente de tempo de execução do sistema configura o espaço de endereço. Em sistemas Unix, geralmente é um espaço de endereço virtual começando em 0 e indo para um grande número de megabytes. Em sistemas embarcados, pode ser bem pequeno. Pode não começar em 0, em qualquer caso. Se o seu aplicativo estiver sendo executado no modo de supervisor ou equivalente, seu ponteiro pode fazer referência a um endereço real, que pode ou não ter backup com memória real.
Você poderia ter um ponteiro para algum lugar dentro de sua memória válida, mesmo dentro de seu segmento de dados, bss, pilha ou heap, mas não apontando para um objeto válido. Uma variante disso é um ponteiro que costumava apontar para um objeto válido, antes que algo de ruim acontecesse ao objeto. Coisas ruins neste contexto incluem desalocação, corrupção de memória ou corrupção de ponteiro.
Você poderia ter um ponteiro totalmente ilegal, como um ponteiro com alinhamento ilegal para o que está sendo referenciado.
O problema fica ainda pior quando você considera arquiteturas baseadas em segmento / deslocamento e outras implementações de ponteiro ímpar. Esse tipo de coisa normalmente é escondido do desenvolvedor por bons compiladores e uso criterioso de tipos, mas se você quiser romper o véu e tentar ser mais esperto do que os desenvolvedores de sistema operacional e compilador, você pode, mas não há uma maneira genérica para fazer isso irá lidar com todos os problemas que você pode encontrar.
A melhor coisa que você pode fazer é permitir o travamento e divulgar algumas informações de diagnóstico de qualidade.
fonte
Este artigo MEM10-C. Definir e usar uma função de validação de ponteiro diz que é possível fazer uma verificação em algum grau, especialmente no sistema operacional Linux.
fonte
Em geral, é impossível fazer. Aqui está um caso particularmente desagradável:
Em muitas plataformas, isso imprimirá:
Você está forçando o sistema de tempo de execução a interpretar incorretamente os bits de memória, mas, neste caso, não vai travar, porque todos os bits fazem sentido. Esta é parte do projeto da linguagem (olhada polimorfismo C-estilo com
struct inaddr
,inaddr_in
,inaddr_in6
), então você não pode proteger de forma confiável contra ela em qualquer plataforma.fonte
É inacreditável a quantidade de informações enganosas que você pode ler nos artigos acima ...
E mesmo na documentação do microsoft msdn, o IsBadPtr foi banido. Muito bem - eu prefiro o aplicativo funcional em vez de travar. Mesmo que o prazo de trabalho possa estar funcionando incorretamente (desde que o usuário final possa continuar com o aplicativo).
Ao pesquisar no Google, não encontrei nenhum exemplo útil para janelas - encontrei uma solução para aplicativos de 32 bits,
http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx?rzp=%2FKB%2Fsystem%2Fdetect-driver%2F%2FDetectDriverSrc.zip&zep=DetectDriverSrc%2FDetectDriver=%2Fsrrcppp&robtid.cc%2Fsrrcpp2FDetectDriver=%2Fsrttppp2Fdetc_pps2Fdetc%2Fdetc%2Fsrpps2Fdetc%2Fsrtt2Fdetc_2 = 2
mas também preciso oferecer suporte a aplicativos de 64 bits, portanto, esta solução não funcionou para mim.
Mas eu colhi os códigos-fonte do wine e consegui preparar um tipo de código semelhante que funcionaria para aplicativos de 64 bits também - anexando o código aqui:
E o código a seguir pode detectar se o ponteiro é válido ou não, você provavelmente precisará adicionar alguma verificação NULL:
Isso obtém o nome da classe do ponteiro - acho que deve ser o suficiente para as suas necessidades.
Uma coisa que ainda temo é o desempenho da verificação de ponteiro - no fragmento de código acima, já existem 3-4 chamadas de API sendo feitas - pode ser um exagero para aplicativos de tempo crítico.
Seria bom se alguém pudesse medir a sobrecarga da verificação de ponteiro em comparação, por exemplo, com chamadas C # / c ++ gerenciado.
fonte
De fato, algo poderia ser feito em uma ocasião específica: por exemplo, se você deseja verificar se uma string de ponteiro de string é válida, usando write (fd, buf, szie) syscall pode ajudá-lo a fazer a mágica: deixe fd ser um descritor de arquivo temporário arquivo que você cria para teste, e buf apontando para a string que você está testando, se o ponteiro for inválido, write () retornaria -1 e errno definido como EFAULT, indicando que buf está fora de seu espaço de endereço acessível.
fonte
A seguir funciona no Windows (alguém sugeriu antes):
A função deve ser um método estático, autônomo ou estático de alguma classe. Para testar em somente leitura, copie os dados no buffer local. Para testar a escrita sem modificar o conteúdo, escreva-os. Você pode testar apenas o primeiro / último endereço. Se o ponteiro for inválido, o controle será passado para 'doSomething' e, em seguida, fora dos colchetes. Só não use nada que exija destruidores, como o CString.
fonte
No Windows, uso este código:
Uso:
fonte
IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () para Windows.
Isso leva tempo proporcional ao comprimento do bloco, portanto, para verificação de integridade, apenas verifico o endereço inicial.
fonte
Já vi várias bibliotecas usarem algum método para verificar a existência de memória não referenciada e assim por diante. Eu acredito que eles simplesmente "substituem" os métodos de alocação e desalocação de memória (malloc / free), que tem alguma lógica que mantém o controle dos ponteiros. Suponho que isso seja um exagero para o seu caso de uso, mas seria uma maneira de fazer isso.
fonte
Tecnicamente, você pode substituir o operador novo (e excluir ) e coletar informações sobre toda a memória alocada, para que possa ter um método para verificar se a memória heap é válida. mas:
você ainda precisa de uma maneira de verificar se o ponteiro está alocado na pilha ()
você precisará definir o que é um ponteiro 'válido':
a) a memória nesse endereço é alocada
b) a memória nesse endereço é o endereço inicial do objeto (por exemplo, o endereço não está no meio de uma grande matriz)
c) a memória nesse endereço é o endereço inicial do objeto do tipo esperado
Resumindo : a abordagem em questão não é a maneira C ++, você precisa definir algumas regras que garantam que a função receba ponteiros válidos.
fonte
Não há como fazer essa verificação em C ++. O que você deve fazer se outro código passar a você um ponteiro inválido? Você deve bater. Por quê? Confira este link: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
fonte
Adendo à (s) resposta (s) aceita (s):
Suponha que seu ponteiro pode conter apenas três valores - 0, 1 e -1 onde 1 significa um ponteiro válido, -1 um inválido e 0 outro inválido. Qual é a probabilidade de seu ponteiro ser NULL, sendo todos os valores igualmente prováveis? 1/3. Agora, retire o caso válido, portanto, para cada caso inválido, você terá uma proporção de 50:50 para capturar todos os erros. Parece bom certo? Dimensione isso para um ponteiro de 4 bytes. Existem 2 ^ 32 ou 4294967294 valores possíveis. Destes, apenas UM valor está correto, um é NULL e você ainda fica com 4294967292 outros casos inválidos. Recalcular: você tem um teste para 1 de (4294967292+ 1) casos inválidos. Uma probabilidade de 2.xe-10 ou 0 para a maioria dos propósitos práticos. Essa é a futilidade da verificação NULL.
fonte
Você sabe, um novo driver (pelo menos no Linux) que é capaz disso provavelmente não seria tão difícil de escrever.
Por outro lado, seria tolice construir seus programas dessa forma. A menos que você tenha algum uso realmente específico e único para tal coisa, eu não o recomendaria. Se você construir um grande aplicativo carregado com verificações constantes de validade de ponteiro, provavelmente será terrivelmente lento.
fonte
Se eles não funcionarem - a próxima atualização do Windows irá consertar? Se eles não funcionarem no nível de conceito - a função provavelmente será removida completamente da API do Windows.
A documentação do MSDN afirma que eles estão proibidos, e a razão para isso é provavelmente uma falha de design posterior do aplicativo (por exemplo, geralmente você não deve comer ponteiros inválidos silenciosamente - se você for responsável pelo design de todo o aplicativo, é claro) e desempenho / tempo de verificação de ponteiro.
Mas você não deve alegar que eles não funcionam por causa de algum blog. Em meu aplicativo de teste, verifiquei se eles funcionam.
fonte
esses links podem ser úteis
_CrtIsValidPointer Verifica se um intervalo de memória especificado é válido para leitura e gravação (somente versão de depuração). http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx
_CrtCheckMemory Confirma a integridade dos blocos de memória alocados no heap de depuração (somente versão de depuração). http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx
fonte