Estou atribuindo valores em um programa C ++ fora dos limites como este:
#include <iostream>
using namespace std;
int main()
{
int array[2];
array[0] = 1;
array[1] = 2;
array[3] = 3;
array[4] = 4;
cout << array[3] << endl;
cout << array[4] << endl;
return 0;
}
O programa imprime 3
e 4
. Não deveria ser possível. Estou usando o g ++ 4.3.3
Aqui está o comando compilar e executar
$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4
Somente ao atribuir array[3000]=3000
isso me causa uma falha de segmentação.
Se o gcc não verificar os limites da matriz, como posso ter certeza se meu programa está correto, pois isso pode levar a alguns problemas sérios mais tarde?
Substituí o código acima por
vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;
e este também não produz erro.
vector
não redimensiona automaticamente ao acessar elementos fora dos limites! É apenas UB!Respostas:
Bem-vindo ao melhor amigo de todos os programadores de C / C ++: comportamento indefinido .
Há muito que não é especificado pelo padrão de idioma, por vários motivos. Este é um deles.
Em geral, sempre que você encontra um comportamento indefinido, tudo pode acontecer. O aplicativo pode falhar, congelar, ejetar sua unidade de CD-ROM ou fazer com que demônios saiam do seu nariz. Pode formatar seu disco rígido ou enviar por e-mail todo o seu pornô para sua avó.
Pode até, se você tiver realmente azar, parecer funcionar corretamente.
A linguagem simplesmente diz o que deve acontecer se você acessar os elementos dentro dos limites de uma matriz. É deixado indefinido o que acontece se você sair dos limites. Pode parecer funcionar hoje, no seu compilador, mas não é legal em C ou C ++, e não há garantia de que ainda funcionará na próxima vez que você executar o programa. Ou que tem os dados essenciais não substituídos, mesmo agora, e você apenas não ter encontrado os problemas, que está indo para a causa - ainda.
Quanto ao motivo pelo qual não há verificação de limites, há alguns aspectos na resposta:
std::vector
modelo de classe, que permite ambos.operator[]
foi projetado para ser eficiente. O padrão de idioma não exige que ele execute verificação de limites (embora também não o proíba). Um vetor também tem aat()
função de membro que é garantida para executar a verificação de limites. Portanto, em C ++, você obtém o melhor dos dois mundos se usar um vetor. Você obtém desempenho semelhante ao de uma matriz sem verificação de limites e pode usar o acesso verificado quando quiser.fonte
Usando g ++, você pode adicionar a opção de linha de comando:
-fstack-protector-all
.No seu exemplo, resultou no seguinte:
Realmente não ajuda a encontrar ou resolver o problema, mas pelo menos o segfault permitirá que você saiba que algo está errado.
fonte
-fsanitize=address
que pega esse bug no momento da compilação (se estiver otimizando) e no tempo de execução.-fsanitize=undefined,address
. Mas é importante notar que existem casos raros de canto com a biblioteca std, quando o acesso fora dos limites não é detectado pelo sanitizer . Por esse motivo, recomendo usar a-D_GLIBCXX_DEBUG
opção adicional, que adiciona ainda mais verificações.O g ++ não verifica os limites da matriz e você pode substituir algo com 3,4, mas nada realmente importante; se você tentar com números mais altos, ocorrerá um acidente.
Você está apenas substituindo partes da pilha que não são usadas, você pode continuar até chegar ao final do espaço alocado para a pilha e ela poderá travar eventualmente
EDIT: Você não tem como lidar com isso, talvez um analisador de código estático possa revelar essas falhas, mas isso é simples demais, você pode ter falhas semelhantes (mas mais complexas) não detectadas, mesmo para analisadores estáticos
fonte
É um comportamento indefinido, tanto quanto eu sei. Execute um programa maior com isso e ele travará em algum lugar ao longo do caminho. A verificação de limites não faz parte de matrizes brutas (ou mesmo std :: vector).
Use std :: vector com
std::vector::iterator
's, para que você não precise se preocupar com isso.Editar:
Apenas por diversão, execute isso e veja quanto tempo até você travar:
Edit2:
Não corra isso.
Edit3:
OK, aqui está uma lição rápida sobre matrizes e seus relacionamentos com ponteiros:
Quando você usa a indexação de matriz, está realmente usando um ponteiro disfarçado (chamado de "referência"), que é automaticamente desreferenciado. É por isso que, em vez de * (matriz [1]), a matriz [1] retorna automaticamente o valor nesse valor.
Quando você tem um ponteiro para uma matriz, assim:
Então a "matriz" na segunda declaração está realmente decaindo para um ponteiro para a primeira matriz. Esse é um comportamento equivalente a isso:
Quando você tenta acessar além do que você alocou, está apenas usando um ponteiro para outra memória (da qual o C ++ não se queixará). Tomando meu programa de exemplo acima, isso é equivalente a isso:
O compilador não irá reclamar porque, na programação, você geralmente precisa se comunicar com outros programas, especialmente o sistema operacional. Isso é feito com ponteiros um pouco.
fonte
Sugestão
Se você quer ter matrizes de tamanho rápido de restrição com verificação de erro gama, tente usar boost :: variedade , (também std :: tr1 :: variedade de
<tr1/array>
será contêiner padrão na próxima especificação C ++). É muito mais rápido que o std :: vector. Ele reserva memória na pilha ou dentro da instância da classe, assim como int array [].Este é um código de exemplo simples:
Este programa imprimirá:
fonte
C ou C ++ não verificará os limites de um acesso à matriz.
Você está alocando a matriz na pilha. A indexação da matriz via
array[3]
é equivalente a *(array + 3)
, onde matriz é um ponteiro para & matriz [0]. Isso resultará em comportamento indefinido.Uma maneira de capturar isso às vezes em C é usar um verificador estático, como splint . Se você executar:
em,
então você receberá o aviso:
fonte
Você certamente está substituindo sua pilha, mas o programa é simples o suficiente para que seus efeitos passem despercebidos.
fonte
Execute isso no Valgrind e você poderá ver um erro.
Como Falaina apontou, o valgrind não detecta muitos casos de corrupção de pilha. Eu apenas tentei a amostra em valgrind, e ela realmente relata zero erros. No entanto, o Valgrind pode ser fundamental para encontrar muitos outros tipos de problemas de memória; nesse caso, não é particularmente útil, a menos que você modifique seu bulid para incluir a opção --stack-check. Se você construir e executar a amostra como
valgrind irá relatar um erro.
fonte
Comportamento indefinido trabalhando a seu favor. Qualquer que seja a memória que você esteja batendo, aparentemente não está segurando nada de importante. Observe que C e C ++ não fazem checagem de limites em matrizes; portanto, coisas assim não serão capturadas no tempo de compilação ou execução.
fonte
Quando você inicializa a matriz com
int array[2]
, o espaço para 2 números inteiros é alocado; mas o identificadorarray
simplesmente aponta para o início desse espaço. Quando você acessaarray[3]
earray[4]
, o compilador simplesmente incrementa esse endereço para apontar para onde esses valores estariam, se a matriz fosse longa o suficiente; tente acessar algo comoarray[42]
sem inicializá-lo primeiro, você acabará recebendo o valor que já estava na memória naquele local.Editar:
Mais informações sobre ponteiros / matrizes: http://home.netcom.com/~tjensen/ptr/pointers.htm
fonte
quando você declara int array [2]; você reserva 2 espaços de memória de 4 bytes cada (programa de 32 bits). se você digitar array [4] no seu código, ele ainda corresponderá a uma chamada válida, mas somente em tempo de execução será lançada uma exceção não tratada. C ++ usa gerenciamento manual de memória. Esta é realmente uma falha de segurança que foi usada para programas de hackers
isso pode ajudar a entender:
int * somepointer;
somepointer [0] = somepointer [5];
fonte
Pelo que entendi, as variáveis locais são alocadas na pilha; portanto, sair dos limites da sua própria pilha só pode sobrescrever alguma outra variável local, a menos que você exagere demais e exceda o tamanho da pilha. Como você não possui outras variáveis declaradas em sua função - isso não causa efeitos colaterais. Tente declarar outra variável / matriz logo após a sua primeira e veja o que acontecerá com ela.
fonte
Quando você escreve 'array [index]' em C, ele o converte nas instruções da máquina.
A tradução é algo como:
O resultado aborda algo que pode ou não fazer parte da matriz. Em troca da velocidade impressionante das instruções da máquina, você perde a rede de segurança do computador que verifica as coisas para você. Se você é meticuloso e cuidadoso, não há problema. Se você é desleixado ou comete um erro, se queima. Às vezes, pode gerar uma instrução inválida que causa uma exceção, às vezes não.
fonte
Uma boa abordagem que eu já vi muitas vezes e que tinha sido usada na verdade é injetar algum elemento do tipo NULL (ou um criado, como
uint THIS_IS_INFINITY = 82862863263;
) no final da matriz.Então, na verificação da condição do loop,
TYPE *pagesWords
há algum tipo de matriz de ponteiro:Essa solução não funcionará se a matriz for preenchida com
struct
tipos.fonte
Como mencionado agora na pergunta, usando std :: vector :: at resolverá o problema e fará uma verificação vinculada antes de acessar.
Se você precisar de uma matriz de tamanho constante localizada na pilha como seu primeiro código, use o novo container std :: array do C ++ 11; como vetor, existe a função std :: array :: at. De fato, a função existe em todos os contêineres padrão nos quais tem um significado, ou seja, onde o operador [] é definido :( deque, map, unordered_map), com exceção do std :: bitset no qual é chamado std :: bitset: :teste.
fonte
A libstdc ++, que faz parte do gcc, possui um modo de depuração especial para verificação de erros. É ativado pelo sinalizador do compilador
-D_GLIBCXX_DEBUG
. Entre outras coisas, ele limita a verificaçãostd::vector
ao custo do desempenho. Aqui está uma demonstração on - line com a versão recente do gcc.Portanto, na verdade, você pode verificar os limites com o modo de depuração libstdc ++, mas deve fazê-lo apenas durante o teste, pois custa um desempenho notável comparado ao modo libstdc ++ normal.
fonte
Se você mudar um pouco o seu programa:
(Alterações em maiúsculas - coloque-as em minúsculas, se você quiser fazer isso.)
Você verá que a variável foo foi lixeira. Seu código irá armazenar valores na matriz inexistente [3] e array [4], e ser capaz de recuperá-los corretamente, mas o armazenamento real usado será de foo .
Portanto, você pode "exceder" os limites da matriz em seu exemplo original, mas com o custo de causar danos em outros lugares - danos que podem ser muito difíceis de diagnosticar.
Por que não há verificação automática de limites - um programa escrito corretamente não precisa dele. Feito isso, não há razão para verificar os limites de tempo de execução, pois isso atrasaria o programa. É melhor entender tudo isso durante o design e a codificação.
O C ++ é baseado em C, que foi projetado para ser o mais próximo possível da linguagem assembly.
fonte