`String.assign (string.data (), 5)` è ben definito o UB?


11

Un collega voleva scrivere questo:

std::string_view strip_whitespace(std::string_view sv);

std::string line = "hello  ";
line = strip_whitespace(line);

Ho detto che il ritorno string_viewmi rendeva inquieto a priori e, inoltre, l'alias qui mi sembrava UB.

Posso affermare con certezza che line = strip_whitespace(line)in questo caso equivale a line = std::string_view(line.data(), 5). Credo che chiamerà string::operator=(const T&) [with T=string_view], che è definito come equivalente line.assign(const T&) [with T=string_view], che è definito come equivalente line.assign(line.data(), 5), che è definito per fare questo:

Preconditions: [s, s + n) is a valid range.
Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).
Returns: *this.

Ma questo non dice cosa succede quando c'è un aliasing.

Ieri ho fatto questa domanda su cpplang Slack e ho ottenuto risposte contrastanti. Alla ricerca di risposte autorevoli qui e / o analisi empiriche delle implementazioni dei veri venditori di biblioteche.


Ho scritto casi di test per string::assign, vector::assign, deque::assign, list::assign, e forward_list::assign.

  • Libc ++ fa funzionare tutti questi casi di test.
  • Libstdc ++ li fa funzionare tutti tranne forward_list, che segfault.
  • Non conosco la biblioteca di MSVC.

Il segfault in libstdc ++ mi fa sperare che questo sia UB; ma vedo anche che libc ++ e libstdc ++ stanno facendo un grande sforzo per farlo funzionare almeno nei casi comuni.


Hai compilato i casi di test con ASan e / o li hai eseguiti sotto Valgrind? Ciò eliminerebbe le ipotesi sul fatto che il codice causi violazioni dell'accesso, anche se potrebbe ancora funzionare nella pratica piuttosto che per definizione.
Konrad Rudolph,

1
"Se una qualsiasi funzione membro o operatore di basic_string genera un'eccezione, quella funzione o operatore non ha altri effetti sull'oggetto basic_string." - questo impone che l'allocazione dello spazio di archiviazione avvenga prima che lo spazio di archiviazione esistente venga liberato, in modo che venga generata un'eccezione se l'allocazione fallisce, senza alterazione *this. Ma non vedo nulla che impedisca il riutilizzo della memoria esistente, nel qual caso ciò non è specificato, poiché la semantica del sovrascrittura della memoria non è specificata.
Sam Varshavchik,


2
Per i contenitori di sequenza menzionati, è sicuramente UB, a causa della violazione delle condizioni preliminari dei assignrequisiti in [tab: container.seq.req] .
noce,

Risposte:


8

Tranne un paio di eccezioni di cui la tua non è una, la chiamata di una funzione membro non const (cioè assign) su una stringa invalida [...] i puntatori ai suoi elementi. Questo viola la precondizione su assignche [s, s + n)è un intervallo valido, quindi questo è un comportamento indefinito.

Si noti che string::operator=(string const&)ha una lingua specifica per rendere l'auto-assegnazione un no-op.


1
Allora, qual è esattamente il punto di invalidamento e il punto in cui la condizione preliminare è richiesta? La risposta sembra presupporre che la condizione preliminare debba essere mantenuta dopo che è stata chiamata la funzione membro.
noce,

1
@walnut Non sono un avvocato linguista (né una persona con una conoscenza C ++ particolarmente estesa), ma quando invertiamo il tuo scenario, possiamo porre una domanda: l'intervallo potrebbe essere invalidato durante l'esecuzione di assign? Se sì, allora dovremmo impostare un punto specifico all'interno dell'implementazione di Assegna per contrassegnare quando può verificarsi esattamente l'invalidazione, e credo che non sia qualcosa che C ++ farebbe. Potrei sbagliarmi però.
Fureeish,

2
@Fureeish Neanche io lo so, ma vedi ad esempio il problema LWG 526 , chiuso come " non un difetto ", che menziona nella sua raccomandazione per la chiusura che std::vector::insert(iterator pos, const T& value)deve funzionare se si valuetrova nel vettore stesso, perché lo standard non specifica che è consentito non funzionare, anche se tale riferimento potrebbe essere invalidato dalla chiamata.
noce,

1
@walnut " è necessario per funzionare perché lo standard non autorizza il suo funzionamento. " - Lo adoro . Quindi ... vale la pena chiedere cosa succede in pratica ? L'implementazione è necessaria per fare una copia dell'argomento in tale situazione? Come potresti realisticamente implementarlo ..? Ho sentito parlare di standard che richiedono ai compilatori di fare l'impossibile - è uno di quei casi? Indipendentemente da ciò, grazie per il commento!
Fureeish,

1
@Fureeish In realtà il mio esempio precedente (ora eliminato) non stava effettivamente testando ciò che volevo testare. Ecco un esempio fisso che mostra che sia libc ++ che libstdc ++ eseguono effettivamente la copia prima di passare alla riallocazione come richiesto.
noce,
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.