Domanda

A seguito di un commento filo caldo in un'altra domanda, sono venuto a discutere di ciò che è e ciò che non è definito nello standard C99 sugli array C.

Fondamentalmente quando definisco una matrice 2D come int a[5][5], fa la GARANZIA C99 standard o non che sarà un blocco contiguo di int, posso gettarlo ai (int *)a ed essere sicuro che avrò una matrice 1D valido 25 int.

Mi pare di capire lo standard della proprietà di cui sopra è implicita nella definizione sizeof e l'aritmetica dei puntatori, ma altri sembra essere d'accordo e dice colata a (int *) la struttura sopra dare un comportamento indefinito (anche se sono d'accordo che tutti implementazioni esistenti in realtà allocare valori contigui).

In particolare, se pensiamo un'implementazione che avrebbero array strumento controlla limiti di matrice per tutte le dimensioni e restituire qualche tipo di errore durante l'accesso matrice 1D, oppure non dare corretto accesso agli elementi di cui sopra prima fila. Potrebbe essere tale implementazione compilant norma? E in questo caso quali parti dello standard C99 sono rilevanti.

È stato utile?

Soluzione

Si dovrebbe iniziare con l'ispezione cosa int a [5] [5] è davvero. I tipi coinvolti sono:

  • int
  • array [5] di int
  • array [5] di array

Non v'è alcun array [25] di int coinvolti.

È vero che la semantica sizeof implicano che l'array nel suo complesso è contiguo. L'array [5] di int deve avere 5 * sizeof (int), e ricorsivamente applicata, un [5] [5] deve avere 5 * 5 * sizeof (int). Non c'è spazio per imbottitura supplementare.

Inoltre, la matrice nel suo complesso deve funzionare quando somministrato a memset, memmove o memcpy con il sizeof. Deve anche essere possibile per scorrere l'intera matrice con un (char *). Così un'iterazione valido è:

int  a[5][5], i, *pi;
char *pc;

pc = (char *)(&a[0][0]);
for (i = 0; i < 25; i++)
{
    pi = (int *)pc;
    DoSomething(pi);
    pc += sizeof(int);
}

Fare lo stesso con un (int *) sarebbe comportamento indefinito, perché, come detto, non c'è array [25] di int coinvolti. Utilizzando un sindacato come nella risposta di Christoph dovrebbe essere valido, anche. Ma c'è un altro punto di complicare ulteriormente questo aspetto, l'operatore di uguaglianza:

6.5.9.6 Due puntatori risultano uguali se e solo se entrambi sono puntatori nulli, entrambi sono puntatori allo stesso oggetto (compreso un puntatore ad un oggetto e un oggetto secondario al suo inizio) o funzione, entrambi sono puntatori ad uno dopo l'ultimo elemento della stessa matrice oggetto, oppure uno è un puntatore a una oltre la fine di un oggetto matrice e l'altro è un puntatore all'inizio di un oggetto array diverso che sembra seguire immediatamente il primo oggetto matrice nello spazio di indirizzi. 91)

91) Due oggetti può essere adiacente nella memoria perché sono elementi adiacenti di un array più grande o elementi adiacenti di una struttura senza imbottitura tra di loro, o perché l'implementazione scelto di posizionare in modo, anche se sono estranei. Se precedenti operazioni del puntatore nullo (come accessi esterni limiti di matrice) prodotte comportamento indefinito, confronti successivi producono anche un comportamento indefinito.

Ciò significa, per questo:

int a[5][5], *i1, *i2;

i1 = &a[0][0] + 5;
i2 = &a[1][0];

I1 confronto uguale a i2. Ma quando l'iterazione sopra la matrice con un (int *), è comunque un comportamento indefinito, perché è originariamente derivato dalla prima sottomatrice. Esso non magicamente converte in un puntatore nella seconda sottoarray.

Anche quando si fa questo

char *c = (char *)(&a[0][0]) + 5*sizeof(int);
int  *i3 = (int *)c;

non aiuterà. Confronta uguale a i1 e i2, ma non è derivato da uno qualsiasi dei sottoarray; è un puntatore a una singola int o un array [1] di int al meglio.

non ritengo questo un bug nella norma. E 'il contrario: Permettere questo introdurrebbe un caso particolare che viola sia il sistema di tipi per gli array o le regole per l'aritmetica dei puntatori o entrambi. Può essere considerato una definizione mancante, ma non un bug.

Quindi, anche se il layout di memoria per un [5] [5] è identico al layout di una [25], e lo stesso ciclo usando un (char *) può essere utilizzato per scorrere su entrambi, un'implementazione è ha permesso di far saltare in aria se utilizzato come gli altri. Non so perché dovrebbe sapere o qualsiasi implementazione che potrebbe, e forse c'è un singolo fatto in standard non menzionati fino ad ora che rende bene il comportamento definito. Fino ad allora, riterrei per essere indefinito e soggiorno sul sicuro.

Altri suggerimenti

Ho aggiunto qualche ulteriore commento al nostro originale discussione .

semantica sizeof implicano che int a[5][5] è contiguo, ma visitare tutti i 25 numeri interi tramite incrementando un puntatore come int *p = *a è un comportamento indefinito: aritmetica puntatore è definito solo fino a quando tutti i puntatori invoved trovano all'interno (o un elemento oltre l'ultimo elemento del) stesso vettore, come ad esempio &a[2][1] e &a[3][1] non (vedi paragrafo 6.5.6 C99).

In linea di principio, è possibile aggirare il problema gettando &a - che ha tipo int (*)[5][5] - a int (*)[25]. Questo è legale secondo 6.3.2.3 § 7, in quanto non viola i requisiti di allineamento. Il problema è che per accedere alle interi attraverso questo nuovo puntatore è illegale in quanto viola le regole di aliasing in 6.5 § 7. È possibile aggirare il problema utilizzando un union per il tipo di giochi di parole (si veda la nota 82 nel TC3):

int *p = ((union { int multi[5][5]; int flat[25]; } *)&a)->flat;

Questa è, per quanto ne so, gli standard C99 compatibile.

Se la matrice è statica, come la matrice int a[5][5], è garantito per essere contigui.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top