Pourquoi les indices de tableau négatifs ont-ils un sens?


14

J'ai rencontré une expérience étrange en programmation C. Considérez ce code:

int main(){
  int array1[6] = {0, 1, 2, 3, 4, 5};
  int array2[6] = {6, 7, 8, 9, 10, 11};

  printf("%d\n", array1[-1]);
  return 0;
}

Lorsque je compile et exécute ceci, je ne reçois aucune erreur ni avertissement. Comme l'a dit mon conférencier, l'index du tableau -1accède à une autre variable. Je suis toujours confus, pourquoi diable un langage de programmation a-t-il cette capacité? Je veux dire, pourquoi autoriser les indices de tableau négatifs?


2
Bien que cette question soit motivée par le C comme langage de programmation concret, je pense qu'elle peut être comprise comme une question conceptuelle qui est ontopique ici (à peine).
Raphael

7
@Raphael Je ne suis pas d'accord et je crois que cela devrait appartenir à SO, de toute façon c'est un comportement non défini dans les manuels (référence à la mémoire en dehors du tableau) et les drapeaux de compilation appropriés devraient en avertir
ratchet freak

Je suis d'accord avec @ratchetfreak. Cela semble être une faille du compilateur car la plage d'index valide est [0, 5]. Tout ce qui est à l'extérieur doit être une erreur de compilation / d'exécution. En général, les vecteurs sont des cas particuliers de fonctions dont le premier indice d'élément appartient à l'utilisateur. Puisque le contrat C est que les éléments commencent à l'index 0, c'est une erreur d'accéder aux éléments négatifs.
Val

2
@Raphael C a deux particularités par rapport aux langages typiques avec des tableaux qui comptent ici. La première est que C a des sous-tableaux et faire référence à l'élément -1d'un sous-tableau est un moyen parfaitement valide de faire référence à l'élément avant ce tableau dans le tableau le plus grand. L'autre est que si l'index n'est pas valide, le programme n'est pas valide, mais dans la plupart des implémentations, vous obtiendrez un mauvais comportement silencieux, pas une erreur hors plage.
Gilles 'SO- arrête d'être méchant'

4
@Gilles Si c'est le point de la question, cela aurait dû en effet être sur Stack Overflow .
Raphael

Réponses:


27

L'opération d'indexation de tableau a[i]tire sa signification des caractéristiques suivantes de C

  1. La syntaxe a[i]est équivalente à *(a + i). Ainsi, il est valable de dire 5[a]d'arriver au 5ème élément de a.

  2. Le pointeur-arithmétique dit que, étant donné un pointeur pet un entier i, p + i le pointeur pavancé par i * sizeof(*p)octets

  3. Le nom d'un tableau adevient très rapidement un pointeur vers le 0ème élément dea

En effet, l'indexation de tableaux est un cas particulier d'indexation de pointeurs. Puisqu'un pointeur peut pointer vers n'importe quel endroit à l'intérieur d'un tableau, toute expression arbitraire qui ressemble à p[-1]n'est pas fausse par examen, et donc les compilateurs ne considèrent pas (ne peuvent pas) toutes ces expressions comme des erreurs.

Votre exemple a[-1]aest en fait le nom d'un tableau n'est pas valide. IIRC, il n'est pas défini s'il y a une valeur de pointeur significative comme résultat de l'expression a - 1où l' aon sait être un pointeur vers le 0ème élément d'un tableau. Ainsi, un compilateur intelligent pourrait détecter cela et le signaler comme une erreur. D'autres compilateurs peuvent toujours être conformes tout en vous permettant de vous tirer une balle dans le pied en vous donnant un pointeur sur un emplacement de pile aléatoire.

La réponse informatique est:

  • En C, l' []opérateur est défini sur des pointeurs, pas sur des tableaux. En particulier, il est défini en termes d'arithmétique du pointeur et de déréférence du pointeur.

  • En C, un pointeur est abstraitement un tuple (start, length, offset)à la condition que 0 <= offset <= length. L'arithmétique du pointeur est essentiellement une arithmétique levée sur le décalage, avec la mise en garde que si le résultat de l'opération viole la condition du pointeur, il s'agit d'une valeur indéfinie. Déréférencer un pointeur ajoute une contrainte supplémentaire à cela offset < length.

  • C a une notion undefined behaviourqui permet à un compilateur de représenter concrètement ce tuple sous la forme d'un nombre unique, et de ne détecter aucune violation de la condition du pointeur. Tout programme qui satisfait la sémantique abstraite sera en sécurité avec la sémantique concrète (avec perte). Tout ce qui viole la sémantique abstraite peut être, sans commentaire, accepté par le compilateur et il peut faire tout ce qu'il veut en faire.


S'il vous plaît essayez de donner une réponse générale, pas une en fonction des particularités d'un langage de programmation particulier.
Raphael

6
@Raphael, la question portait explicitement sur C. Je pense avoir abordé la question spécifique de savoir pourquoi un compilateur C est autorisé à compiler une expression apparemment dénuée de sens dans la définition de C.
Hari

Les questions sur C en particulier sont hors sujet ici; noter mon commentaire sur la question.
Raphael

5
Je pense que l'aspect linguistique comparatif de la question est toujours utile. Je crois avoir donné une description assez "informatisée" des raisons pour lesquelles une implémentation spécifique présentait une sémantique concrète spécifique.
Hari

15

Les tableaux sont simplement présentés comme des morceaux de mémoire contigus. Un accès à un tableau tel qu'un [i] est converti en un accès à l' adresse d' emplacement de mémoire Of (a) + i. Ce code a[-1]est parfaitement compréhensible, il se réfère simplement à l'adresse avant le début du tableau.

Cela peut sembler fou, mais il y a plusieurs raisons pour lesquelles cela est autorisé:

  • il est coûteux de vérifier si l'index i d'un [-] est dans les limites du tableau.
  • certaines techniques de programmation exploitent en fait le fait qui a[-1]est valide. Par exemple, si je sais que ce an'est pas réellement le début du tableau, mais un pointeur au milieu du tableau, il a[-1]obtient simplement l'élément du tableau qui se trouve à gauche du pointeur.

6
En d'autres termes, il ne devrait probablement pas être utilisé. Période. Quoi, vous vous appelez Donald Knuth et vous essayez d'enregistrer 17 autres instructions? Par tous les moyens, allez-y.
Raphael

Merci pour la réponse, mais je n'ai pas eu l'idée. BTW je vais le lire encore et encore jusqu'à ce que je comprenne .. :)
Mohammed Fawzan

2
@Raphael: L'implémentation du modèle d'objet cola utilise la position -1 pour stocker la table virtuelle: piumarta.com/software/cola/objmodel2.pdf . Ainsi, les champs sont stockés dans la partie positive de l'objet et la table virtuelle dans le négatif. Je ne me souviens pas des détails, mais je pense que c'est lié à la cohérence.
Dave Clarke

@ DeZéroToxin: Un tableau est vraiment juste un emplacement en mémoire, avec quelques emplacements à côté qui font logiquement partie du tableau. Mais vraiment, un tableau n'est qu'un pointeur.
Dave Clarke

1
@Raphael, a[-1]est parfaitement logique pour certains cas a, dans ce cas particulier, il est tout simplement illégal (mais pas capturé par le compilateur)
vonbrand

4

Comme les autres réponses l'expliquent, il s'agit d' un comportement indéfini en C. Considérez que C a été défini (et est principalement utilisé) comme un "assembleur de haut niveau". Les utilisateurs de C l'apprécient pour sa vitesse sans compromis, et la vérification des choses à l'exécution est (principalement) hors de question pour des raisons de performances. Certaines constructions en C qui semblent absurdes pour les personnes venant d'autres langages ont un sens parfait en C, comme ceci a[-1]. Oui, cela n'a pas toujours de sens (


1
J'aime cette réponse. Donne une vraie raison pour laquelle cela va bien.
darxsys

3

On peut utiliser une telle fonctionnalité pour écrire des méthodes d'allocation de mémoire qui accèdent directement à la mémoire. Une telle utilisation consiste à vérifier le bloc de mémoire précédent à l'aide d'un index de tableau négatif pour déterminer si les deux blocs peuvent être fusionnés. J'ai utilisé cette fonctionnalité lorsque je développe un gestionnaire de mémoire non volatile.


2

C n'est pas fortement tapé. Un compilateur C standard ne vérifierait pas les limites du tableau. L'autre chose est qu'un tableau en C n'est rien d'autre qu'un bloc de mémoire contigu et l'indexation commence à 0, donc un index de -1 est l'emplacement de tout motif binaire avant a[0].

D'autres langues exploitent les indices négatifs de manière agréable. En Python, a[-1]renverra le dernier élément, a[-2]renverra l'avant-dernier élément et ainsi de suite.


2
Comment les indices de typage et de tableau forts sont-ils liés? Existe-t-il des langues avec un type pour les naturels où les indices de tableau doivent être naturels?
Raphael

@Raphael Pour autant que je sache, une frappe forte signifie que des erreurs de frappe sont détectées. Un tableau est un type, IndexOutOfBounds est une erreur donc dans un langage fortement typé cela sera signalé, en C ce ne sera pas. C'est ce que je voulais dire.
saadtaame

Dans les langues que je connais, les indices matriciels sont de type int, donc a[-5]et, plus généralement, int i; ... a[i] = ...;sont correctement typés. Les erreurs d'index ne sont détectées qu'au moment de l'exécution. Bien sûr, un compilateur intelligent peut détecter certaines violations.
Raphael

@Raphael Je parle du type de données du tableau dans son ensemble, pas des types d'index. Cela explique pourquoi C permet aux utilisateurs d'écrire un [-5]. Oui, -5 est le type d'index correct, mais il est hors limites et c'est une erreur. Il n'y a aucune mention de compilation ou de type d'exécution dans ma réponse.
saadtaame

1

En termes simples:

Toutes les variables (y compris les tableaux) en C sont stockées en mémoire. Disons que vous avez 14 octets de "mémoire" et que vous initialisez ce qui suit:

int a=0;
int array1[6] = {0, 1, 2, 3, 4, 5};

Considérez également la taille d'un entier comme 2 octets. Ensuite, hypothétiquement, dans les 2 premiers octets de mémoire, l'entier a sera enregistré. Dans les 2 octets suivants, l'entier de la première position du tableau sera enregistré (cela signifie tableau [0]).

Ensuite, lorsque vous dites tableau [-1], c'est comme faire référence à l'entier enregistré en mémoire juste avant le tableau [0], qui dans notre hypothèse, est un entier a. En réalité, ce n'est pas exactement la façon dont les variables sont stockées en mémoire.


0
//:Example of negative index:
//:A memory pool with a heap and a stack:

unsigned char memory_pool[64] = {0};

unsigned char* stack = &( memory_pool[ 64 - 1] );
unsigned char* heap  = &( memory_pool[ 0     ] );

int stack_index =    0;
int  heap_index =    0;

//:reserve 4 bytes on stack:
stack_index += 4;

//:reserve 8 bytes on heap:
heap_index  += 8;

//:Read back all reserved memory from stack:
for( int i = 0; i < stack_index; i++ ){
    unsigned char c = stack[ 0 - i ];
    //:do something with c
};;
//:Read back all reserved memory from heap:
for( int i = 0; i < heap_index; i++ ){
    unsigned char c = heap[ 0 + i ];
    //:do something with c
};;

Bienvenue sur CS.SE! Nous recherchons des réponses accompagnées d'explications ou d'une description de la lecture. Nous ne sommes pas un site de codage et nous ne voulons pas de réponses qui ne soient qu'un bloc de code. Vous pourriez vous demander si vous pouvez modifier votre réponse pour fournir ce type d'informations. Je vous remercie!
DW
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.