Quali di queste nuove espressioni con array di caratteri sono ben formate?
Per il seguente programma :
int main()
{
new char[4] {"text"}; // #1
new char[5] {"text"}; // #2
new char[] {"text"}; // #3
}
clang dà un errore per il #1
quale dice:
error: initializer-string for char array is too long
e accetta #2
e #3
.
gcc restituisce il seguente errore per tutte le istruzioni:
error: invalid conversion from 'const char*' to 'char' [-fpermissive]
e inoltre #3
dà l'errore:
error: expected primary-expression before ']' token
Quindi cosa dice la lingua sul fatto che questo codice sia ben formato?
Voglio conoscere le regole attuali, ma sarei anche interessato a sapere se questo è cambiato nelle versioni precedenti della lingua.
Risposte
OK, è abbastanza semplice da rintracciare. La presenza di {}
significa che viene eseguita l'inizializzazione dell'elenco, quindi possiamo visitare la nostra parte preferita delle specifiche: [dcl.init.list] / 3 .
L'oggetto inizializzato nel caso 1 è un file char[4]
. La lista con parentesi graffe non è un inizializzatore designato, quindi 3.1 viene ignorato. char[4]
non è una classe, quindi 3.2 viene ignorato. Questo ci porta a 3.3 :
Altrimenti, se
T
è un array di caratteri e l'elenco degli inizializzatori ha un singolo elemento che è una stringa letterale appropriatamente tipizzata ([dcl.init.string]), l'inizializzazione viene eseguita come descritto in tale sottopunto.
Bene, char[4]
è sicuramente un array di caratteri e l'elenco degli inizializzatori contiene sicuramente un singolo elemento, e quell'elemento in effetti corrisponde al tipo di array di caratteri. Quindi via a [dcl.init.string] andiamo.
Questo ci dice (in qualche modo):
I caratteri successivi del valore del letterale stringa inizializzano gli elementi dell'array.
Ma il paragrafo successivo avverte:
Non ci devono essere più inizializzatori di quanti sono gli elementi dell'array.
Bene, questo rende il numero 1 mal formato.
Quindi, ripetiamo il processo per char[5]
. E questo non si attiva, poiché 5 è sufficientemente grande.
Infine, veniamo a char[]
. Che non è diverso dall'uso di un numero, per quanto riguarda l'inizializzazione. char[]
è un array di caratteri, quindi segue le regole precedenti. C ++ 17 sarebbe soffocare sull'utilizzo char[]
in new
un'espressione, ma C ++ 20 è bene con esso .
Se il tipo-id o il nuovo-tipo-id denota un tipo di matrice di limite sconosciuto ([dcl.array]), il nuovo inizializzatore non deve essere omesso; l'oggetto allocato è un array con n elementi, dove n è determinato dal numero di elementi iniziali forniti nel nuovo inizializzatore ([dcl.init.aggr], [dcl.init.string]).
Il che significa che il n. 2 e il n. 3 dovrebbero essere legali. Quindi il GCC ha torto a renderli mal formati. E rende il numero 1 mal formato per il motivo sbagliato.
Clang è corretto in quanto il numero 1 è mal formato e il numero 2 va bene.
Come ha notato Ted Lyngmo in un commento, # 3 non era valido per le regole grammaticali del C ++, fino a quando il documento P1009R2 non ha apportato una modifica per consentirlo. Una nuova espressione semplicemente non consentiva la possibilità di vuoto []
nel tipo, rimasto da quando non c'era sintassi per inizializzare l'array creato da una nuova espressione , e quindi nessun modo per un compilatore di determinare la dimensione effettiva. Le modifiche del documento sono accettate in C ++ 20 (ma gli autori di compilatori a volte scelgono di supportare "correzioni" retroattivamente nelle -std=
modalità precedenti ).
Per la differenza tra # 1 e # 2, l'inizializzazione dell'oggetto array è specificata in [expr.new] per seguire le regole di inizializzazione diretta di [dcl.init]. Le regole generali per l'inizializzazione in [dcl.init] all'inizio dicono che se l'inizializzatore è una lista di inizializzazione rinforzata , è inizializzazione di lista. Le regole per questo in [dcl.init.list] vanno come:
L'inizializzazione da elenco di un oggetto o di un riferimento di tipo
T
è definita come segue:
[Solo C ++ 20:] Se l' elenco di inizializzazione con parentesi graffe contiene un elenco di inizializzatori designati , ...
If
T
è una classe aggregata e ...Altrimenti, se
T
è un array di caratteri e l'elenco degli inizializzatori ha un singolo elemento che è una stringa letterale appropriatamente tipizzata ([dcl.init.string]), l'inizializzazione viene eseguita come descritto in tale sottopunto....
E così [dcl.init.string] ( C ++ 17 , più recente ) fornisce le regole di inizializzazione effettive che si applicano a questo codice:
Un array di {C ++ 17: tipo di carattere stretto} {C ++ 20: tipo di carattere ordinario ([basic.fundamental])},
char8_t
array,char16_t
array,char32_t
array owchar_t
array può essere inizializzato da {C ++ 17: a narrow} {C ++ 20: un valore letterale stringa normale}, valore letterale stringa UTF-8, valore letterale stringa UTF-16, valore letterale stringa UTF-32 o valore letterale stringa largo, rispettivamente, o da un valore letterale stringa digitato in modo appropriato racchiuso tra parentesi graffe ([lex.string]). I caratteri successivi del valore del letterale stringa inizializzano gli elementi dell'array.Non ci devono essere più inizializzatori di quanti sono gli elementi dell'array. [ Esempio:
char cv[4] = "asdf"; // error
è mal formato poiché non c'è spazio per il trailing implicito
'\0'
. - esempio finale ]Se ci sono meno inizializzatori di quanti sono gli elementi dell'array, ogni elemento non inizializzato esplicitamente deve essere inizializzato con zero ([dcl.init]).
Proprio come la definizione di variabile semplice, quando il tipo di matrice di caratteri di una nuova espressione ha un limite specificato, deve essere abbastanza grande per tutti i caratteri di una stringa letterale che lo inizializza, incluso il carattere null finale.
(Questa è una vecchia differenza tra C e C ++: C consente char cv[4] = "asdf";
e ignora il carattere null.)