Perché le matrici C non possono avere lunghezza 0?


13

Lo standard C11 afferma che gli array, sia di dimensioni che di lunghezza variabile "devono avere un valore maggiore di zero". Qual è la giustificazione per non consentire una lunghezza di 0?

Soprattutto per array di lunghezza variabile ha perfettamente senso avere una dimensione di zero ogni tanto. È anche utile per le matrici statiche quando le loro dimensioni provengono da un'opzione di configurazione macro o build.

È interessante notare che GCC (e clang) forniscono estensioni che consentono matrici di lunghezza zero. Java consente anche matrici di lunghezza zero.


7
stackoverflow.com/q/8625572 ... "Un array di lunghezza zero sarebbe complicato e confuso per riconciliarsi con il requisito che ogni oggetto abbia un indirizzo univoco."
Robert Harvey,

3
@RobertHarvey: dato struct { int p[1],q[1]; } foo; int *pp = p+1;, ppsarebbe un puntatore legittimo, ma *ppnon avrebbe un indirizzo univoco. Perché la stessa logica non può valere con un array di lunghezza zero? Dire che dato int q[0]; all'interno di una struttura , qsi riferirebbe a un indirizzo la cui validità sarebbe come quella p+1dell'esempio sopra.
supercat

@DocBrown Dallo standard C11 6.7.6.2.5 parlando dell'espressione utilizzata per determinare la dimensione di un VLA "... ogni volta che viene valutato deve avere un valore maggiore di zero." Non so di C99 (e sembra strano che lo cambino) ma sembra che tu non possa avere una lunghezza pari a zero.
Kevin Cox,

@KevinCox: è disponibile una versione online gratuita dello standard C11 (o della parte in questione)?
Doc Brown,

La versione finale non è disponibile gratuitamente (che peccato) ma è possibile scaricare bozze. L'ultima bozza disponibile è open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf .
Kevin Cox,

Risposte:


11

Il problema che vorrei scommettere è che le matrici C sono solo dei puntatori all'inizio di un pezzo di memoria allocato. Avere una dimensione 0 significherebbe che hai un puntatore a ... niente? Non puoi avere nulla, quindi ci sarebbe dovuto essere una scelta arbitraria. Non puoi usarlo null, perché allora i tuoi array di lunghezza 0 sembrerebbero puntatori nulli. E a quel punto ogni diversa implementazione sceglierà comportamenti arbitrari diversi, portando al caos.



7
@delnan: Beh, se vuoi essere pedante al riguardo, l'aritmetica di array e puntatori è definita in modo tale che un puntatore possa essere convenientemente usato per accedere a un array o per simulare un array. In altre parole, è l'aritmetica del puntatore e l'indicizzazione dell'array che sono equivalenti in C. Ma il risultato è lo stesso comunque ... se la lunghezza dell'array è zero, non stai ancora indicando nulla.
Robert Harvey,

3
@RobertHarvey Tutto vero, ma le tue parole di chiusura (e l'intera risposta in retrospettiva) sembrano solo un modo confuso e confuso per spiegare che un tale array ( penso che sia quello che questa risposta chiama "un pezzo di memoria allocato"?) Avrebbe sizeof0 e come ciò causerebbe problemi. Tutto ciò può essere spiegato usando i concetti e la terminologia appropriati senza perdita di brevità o chiarezza. Mescolare matrici e puntatori rischia solo di diffondere il malinteso matrici = puntatori (che è più importante in altri contesti) senza alcun vantaggio.

2
" Non puoi usare null, perché allora i tuoi array di lunghezza 0 sembrerebbero puntatori null " - in realtà è esattamente quello che fa Delphi. I dynarrays vuoti e i longstring vuoti sono puntatori tecnicamente nulli.
JensG,

3
-1, sono pieno con @delnan qui. Ciò non spiega nulla, soprattutto nel contesto di ciò che l'OP ha scritto su alcuni importanti compilatori a supporto del concetto di array di lunghezza zero. Sono abbastanza sicuro che array di lunghezza zero possano essere forniti in C in modo indipendente dall'implementazione, non "portando al caos".
Doc Brown,

6

Diamo un'occhiata a come un array è in genere disposto in memoria:

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

Si noti che non esiste un oggetto separato denominato arrche memorizza l'indirizzo del primo elemento; quando una matrice appare in un'espressione, C calcola l'indirizzo del primo elemento secondo necessità.

Quindi, cerchiamo di pensare a questo: un array di 0 elementi non avrebbe nessun deposito insieme a parte per esso, il che significa non c'è niente da calcolare l'indirizzo di matrice da (in altre parole, non c'è alcun mapping oggetto per l'identificatore). È come dire "Voglio creare unint variabile che non occupi memoria". È un'operazione senza senso.

modificare

Gli array Java sono animali completamente diversi dagli array C e C ++; non sono un tipo primitivo, ma un tipo di riferimento derivatoObject .

Modifica 2

Un punto evidenziato nei commenti seguenti: il vincolo "maggiore di 0" si applica solo agli array in cui la dimensione è specificata tramite un'espressione costante ; a un VLA è consentito avere una lunghezza 0 Dichiarare un VLA con un'espressione non costante a valore 0 non costituisce una violazione del vincolo, ma invoca un comportamento indefinito.

È chiaro che i VLA sono animali diversi dalle normali matrici e la loro implementazione può consentire una taglia 0 . Non possono essere dichiarati statico nell'ambito del file, poiché la dimensione di tali oggetti deve essere nota prima dell'avvio del programma.

Inoltre, non vale nulla che a partire dal C11, le implementazioni non sono necessarie per supportare i VLA.


3
Scusa, ma IMHO ti manca il punto, proprio come Telastyn. Le matrici di lunghezza zero possono avere molto senso e le implementazioni esistenti come quelle di cui ci ha parlato l'OP dimostrano che può essere fatto.
Doc Brown,

@DocBrown: In primo luogo, mi stavo chiedendo perché lo standard linguistico molto probabilmente non lo consente. In secondo luogo, vorrei un esempio in cui un array a lunghezza 0 ha senso, perché onestamente non riesco a pensarne uno. L'implementazione più probabile è trattarla T a[0]come T *a, ma allora perché non utilizzarla T *a?
John Bode,

Mi dispiace, ma non compro il "ragionamento teorico" sul perché lo standard lo proibisca. Leggi la mia risposta su come l'indirizzo potrebbe essere effettivamente calcolato facilmente. E ti suggerisco di seguire il link nel primo commento di Robert Harveys sotto la domanda e leggere la seconda risposta, c'è un esempio utile.
Doc Brown,

@DocBrown: Ah. L' structhack. Non l'ho mai usato personalmente; non ha mai lavorato su un problema che richiedesse un structtipo di dimensioni variabili .
John Bode,

2
E non dimenticare AFAIK dal C99, C consente matrici di lunghezza variabile. E quando la dimensione dell'array è un parametro, non dover trattare un valore di 0 come un caso speciale può semplificare molti programmi.
Doc Brown,

2

Solitamente si vorrebbe che l'array di dimensioni zero (in realtà variabile) ne conoscesse le dimensioni in fase di esecuzione. Quindi impacchettalo in a structe usa membri di array flessibili , come ad esempio:

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

Ovviamente il membro flessibile dell'array deve essere l'ultimo nella sua struct e deve avere qualcosa prima. Spesso ciò sarebbe correlato alla lunghezza effettiva occupata dal runtime di quel membro flessibile dell'array.

Naturalmente assegneresti:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK, questo era già possibile in C99 ed è molto utile.

A proposito, i membri flessibili dell'array non esistono in C ++ (perché sarebbe difficile definire quando e come dovrebbero essere costruiti e distrutti). Vedi comunque il futuro std :: dynarray


Sai, potrebbero essere limitati a tipi banali e non ci sarebbero difficoltà.
Deduplicatore,

2

Se l'espressione type name[count]è scritta in qualche funzione, si dice al compilatore C di allocare sizeof(type)*countbyte sullo stack frame e calcolare l'indirizzo del primo elemento nell'array.

Se l'espressione type name[count]è scritta al di fuori di tutte le funzioni e definisce le definizioni, allora si dice al compilatore C di allocare sui sizeof(type)*countbyte del segmento di dati e calcolare l'indirizzo del primo elemento nell'array.

namein realtà è un oggetto costante che memorizza l'indirizzo del primo elemento nella matrice e ogni oggetto che memorizza un indirizzo di una certa memoria è chiamato puntatore, quindi questa è la ragione che trattate namecome un puntatore piuttosto che una matrice. Si noti che le matrici in C sono accessibili solo tramite puntatori.

Se countè un'espressione costante che restituisce zero, si dice al compilatore C di allocare zero byte sul frame dello stack o sul segmento di dati e restituire l'indirizzo del primo elemento nell'array, ma il problema nel fare ciò è che il primo elemento di array di lunghezza zero non esiste e non è possibile calcolare l'indirizzo di qualcosa che non esiste.

Questo è razionale quell'elemento n. count+1non esiste countnell'array -length, quindi questo è il motivo per cui il compilatore C proibisce di definire l'array a lunghezza zero come variabile dentro e fuori una funzione, perché qual è il contenuto di nameallora? Quale indirizzoname memorizza esattamente?

Se pè un puntatore, l'espressione p[n]è equivalente a*(p + n)

Dove l'asterisco * nell'espressione giusta è un'operazione di dereference del puntatore, che significa accedere alla memoria puntata p + no accedere alla memoria il cui indirizzo è memorizzato p + n, dove p + nè l'espressione del puntatore, prende l'indirizzo di pe aggiunge a questo indirizzo il numero nmoltiplica il dimensione del tipo di puntatore p.

È possibile aggiungere un indirizzo e un numero?

Sì, è possibile, poiché l'indirizzo è un numero intero senza segno comunemente rappresentato in notazione esadecimale.


Molti compilatori erano soliti consentire dichiarazioni di array di dimensioni zero prima che lo Standard lo vietasse e molti continuano a consentire tali dichiarazioni come estensione. Tali dichiarazioni non causeranno alcun problema se si riconosce che un oggetto di dimensione Nha N+1indirizzi associati, il primo Ndei quali identifica byte univoci e l'ultimo Ndei quali ogni punto è appena passato uno di quei byte. Una tale definizione funzionerebbe bene anche nel caso degenerato dove Nè 0.
supercat

1

Se si desidera un puntatore a un indirizzo di memoria, dichiararne uno. Un array in realtà punta a un pezzo di memoria che hai prenotato. Le matrici decadono ai puntatori quando passano alle funzioni, ma se la memoria a cui puntano è nell'heap, nessun problema. Non vi è alcun motivo per dichiarare un array di dimensioni zero.


2
Generalmente non lo faresti direttamente ma a seguito di una macro o quando dichiari un array a lunghezza variabile con dati dinamici.
Kevin Cox,

Un array non punta mai. Può contenere puntatori e nella maggior parte dei contesti in realtà si utilizza un puntatore al primo elemento, ma questa è una storia diversa.
Deduplicatore,

1
Il nome dell'array È un puntatore costante alla memoria contenuta nell'array.
ncmathsadist,

1
No, il nome dell'array decade in un puntatore al primo elemento, nella maggior parte dei contesti. La differenza è spesso cruciale.
Deduplicatore,

1

Dai tempi del C89 originale, quando uno standard C specificava che qualcosa aveva un comportamento indefinito, ciò significava "Fare qualunque cosa renderebbe un'implementazione su una particolare piattaforma di destinazione più adatta allo scopo previsto". Gli autori dello standard non volevano provare a indovinare quali comportamenti potrebbero essere più adatti per uno scopo particolare. Le implementazioni C89 esistenti con estensioni VLA potrebbero avere avuto comportamenti diversi, ma logici, quando le loro dimensioni erano pari a zero (ad esempio, alcuni potrebbero aver trattato l'array come un'espressione di indirizzo che produce NULL, mentre altri lo trattano come un'espressione di indirizzo che potrebbe eguagliare l'indirizzo di un'altra variabile arbitraria, ma potrebbe tranquillamente aggiungervi zero senza intrappolare). Se un codice avesse fatto affidamento su un comportamento così diverso, gli autori dello Standard non avrebbero "

Invece di provare a indovinare cosa potrebbero fare le implementazioni, o suggerendo che qualsiasi comportamento debba essere considerato superiore a qualsiasi altro, gli autori dello Standard hanno semplicemente permesso agli implementatori di usare il giudizio nel gestire quel caso nel modo che ritengono più opportuno. Le implementazioni che usano malloc () dietro le quinte potrebbero trattare l'indirizzo dell'array come NULL (se malloc size-zero produce null), quelle che usano i calcoli di stack-address potrebbero produrre un puntatore che corrisponde all'indirizzo di qualche altra variabile, e alcune altre implementazioni potrebbero fare altre cose. Non credo che si aspettassero che gli autori di compilatori avrebbero fatto di tutto per far sì che il case d'angolo a dimensione zero si comportasse in modo deliberatamente inutile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.