Índice de matriz fora do limite em C

Por que C diferencia-se no caso de um índice de array fora do limite

 #include  int main() { int a[10]; a[3]=4; a[11]=3;//does not give segmentation fault a[25]=4;//does not give segmentation fault a[20000]=3; //gives segmentation fault return 0; } 

Eu entendo que ele está tentando acessar a memory alocada para processar ou thread no caso de a[11] ou a[25] e ele está saindo de limites de pilha no caso de a[20000] .

Por que o compilador ou o vinculador não dão um erro, eles não estão cientes do tamanho da matriz? Se não, como o tamanho de sizeof(a) funciona corretamente?

O problema é que C / C ++ na verdade não faz qualquer verificação de limite com relação a matrizes. Depende do sistema operacional para garantir que você esteja acessando a memory válida.

Nesse caso específico, você está declarando uma matriz baseada em pilha. Dependendo da implementação específica, o access fora dos limites da matriz simplesmente acessará outra parte do espaço de pilha já alocado (a maioria dos sistemas operacionais e threads reservam uma certa porção de memory para a pilha). Contanto que você esteja brincando no espaço de pilha pré-alocado, tudo não irá falhar (note que eu não disse trabalho).

O que está acontecendo na última linha é que você accessu agora além da parte da memory alocada para a pilha. Como resultado, você está indexando em uma parte da memory que não está alocada para o processo ou está alocada de maneira somente leitura. O sistema operacional vê isso e envia uma falha seg para o processo.

Essa é uma das razões pelas quais o C / C ++ é tão perigoso quando se trata de verificação de limite.

O segfault não é uma ação intencional do seu programa em C que lhe diria que um índice está fora dos limites. Pelo contrário, é uma consequência não intencional do comportamento indefinido.

Em C e C ++, se você declarar uma matriz como

 type name[size]; 

Você só tem permissão para acessar elementos com índices de 0 até o size-1 . Qualquer coisa fora desse intervalo causa um comportamento indefinido. Se o índice estava perto do intervalo, provavelmente você leu a memory do seu próprio programa. Se o índice estiver muito fora do intervalo, provavelmente o seu programa será morto pelo sistema operacional. Mas você não pode saber, tudo pode acontecer.

Por que C permite isso? Bem, a essência básica de C e C ++ é não fornecer resources se eles custam desempenho. C e C ++ tem sido usado há séculos para sistemas críticos de alto desempenho. C tem sido usado como uma linguagem de implementação para kernels e programas em que o access fora dos limites de array pode ser útil para obter access rápido aos objects que ficam adjacentes na memory. Tendo o compilador proibir isso seria em vão.

Por que não avisa sobre isso? Bem, você pode colocar níveis de alerta altos e esperar pela misericórdia do compilador. Isso é chamado de qualidade de implementação (QoI). Se algum compilador usar comportamento aberto (como comportamento indefinido) para fazer algo bom, ele terá uma boa qualidade de implementação nesse aspecto.

 [js@HOST2 cpp]$ gcc -Wall -O2 main.c main.c: In function 'main': main.c:3: warning: array subscript is above array bounds [js@HOST2 cpp]$ 

Se em vez disso, formatar o disco rígido ao ver o array acessado fora dos limites – o que seria legal para ele – a qualidade da implementação seria bastante ruim. Eu gostei de ler sobre esse material no documento ANSI C Rationale .

Você geralmente só recebe uma falha de segmentação se tentar acessar a memory que o seu processo não possui.

O que você está vendo no caso de a[11] (e a[10] a propósito) é a memory que seu processo possui, mas não pertence ao array a[] . a[25000] está tão longe de a[] , provavelmente está fora de sua memory completamente.

Alterar a[11] é muito mais insidioso, pois afeta silenciosamente uma variável diferente (ou o quadro de pilha que pode causar uma falha de segmentação diferente quando a function retorna).

C não está fazendo isso. O subsistema memeory virtual do sistema operacional é.

No caso em que você está apenas um pouco fora do limite, você está endereçando memeory que está alocado para seu programa (na pilha de chamadas de pilha, neste caso). No caso em que você está muito fora dos limites, você está endereçando a memory não entregue ao seu programa e o SO está lançando uma falha de segmentação.

Em alguns sistemas, há também um conceito reforçado de sistema operacional de memory “gravável”, e você pode estar tentando gravar no memeory que possui, mas está marcado como não-gravável.

Apenas para adicionar o que outras pessoas estão dizendo, você não pode confiar no programa simplesmente travando nesses casos, não há garantia do que acontecerá se você tentar acessar um local de memory além dos “limites da matriz”. É o mesmo que se você fizesse algo como:

 int *p; p = 135; *p = 14; 

Isso é apenas random; isso pode funcionar. Pode não ser. Não faça isso. Código para evitar esses tipos de problemas.

Essa não é uma questão C, é um problema no sistema operacional. Seu programa recebeu um certo espaço de memory e qualquer coisa que você fizer dentro disso está bem. A falha de segmentação só acontece quando você acessa a memory fora do seu espaço de processo.

Nem todos os sistemas operacionais têm espaços de endereço separados para cada processo, caso em que você pode corromper o estado de outro processo ou do sistema operacional sem aviso.

Como já mencionado, alguns compiladores podem detectar alguns accesss de array fora dos limites em tempo de compilation. Mas a verificação de limites em tempo de compilation não captura tudo:

 int a[10]; int i = some_complicated_function(); printf("%d\n", a[i]); 

Para detectar isso, as verificações de tempo de execução teriam que ser usadas e elas são evitadas em C devido ao impacto no desempenho. Mesmo com o conhecimento do tamanho do array de um em tempo de compilation, ie sizeof (a), ele não pode proteger contra isso sem inserir uma verificação de tempo de execução.

Pelo que entendi a pergunta e os comentários, você entende por que coisas ruins podem acontecer quando você acessa a memory fora dos limites, mas você está se perguntando por que seu compilador específico não avisou você.

Compiladores estão autorizados a avisá-lo, e muitos fazem nos níveis mais altos de aviso. No entanto, o padrão é escrito para permitir que as pessoas executem compiladores para todos os tipos de dispositivos e compiladores com todos os tipos de resources, de modo que o padrão exige o mínimo possível, garantindo que as pessoas possam realizar trabalhos úteis.

Existem algumas vezes que o padrão requer que um determinado estilo de codificação gere um diagnóstico. Existem várias outras vezes em que o padrão não requer um diagnóstico. Mesmo quando um diagnóstico é necessário, eu não estou ciente de nenhum lugar onde o padrão diz qual deve ser o texto exato.

Mas você não está completamente no frio aqui. Se o seu compilador não avisar você, o Lint poderá. Além disso, há várias ferramentas para detectar tais problemas (em tempo de execução) para matrizes no heap, sendo que um dos mais famosos é o Electric Dill (ou DUMA ). Mas até mesmo a Electric Fence não garante que vai pegar todos os erros de overrun.

A filosofia C é sempre confiar no programador. E também não verificar limites permite que um programa C seja executado mais rapidamente.