Quali di queste nuove espressioni con array di caratteri sono ben formate?

Aug 23 2020

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 #1quale dice:

error: initializer-string for char array is too long

e accetta #2e #3.

gcc restituisce il seguente errore per tutte le istruzioni:

error: invalid conversion from 'const char*' to 'char' [-fpermissive]

e inoltre #3dà 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

11 NicolBolas Aug 23 2020 at 20:21

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 newun'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.

1 aschepler Aug 23 2020 at 20:30

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_­tarray, char16_­tarray, char32_­tarray o wchar_­tarray 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.)