Salta al contenuto
Note per Studenti Esercizio - Quiz su operatore =, dangling pointer, enumerazioni e at() (quarto appello 2021-22, quiz)

Esercizio - Quiz su operatore =, dangling pointer, enumerazioni e at() (quarto appello 2021-22, quiz)

Esame

Quiz del quarto appello del corso di Laboratorio di Programmazione, Ing. Informatica UniPD, a.a. 2021-22 (12 settembre 2022, 90 minuti): dieci domande a quattro affermazioni vero/falso (giusta +1+1, sbagliata −0.25-0.25, non data 00). Gli altri esercizi dell'appello: Esercizio - Shallow copy e deep copy (quarto appello 2021-22, teoria), Esercizio - Template, specializzazione e deduzione degli argomenti (quarto appello 2021-22, teoria), Esercizio - RAII e garanzie sulle eccezioni (quarto appello 2021-22, teoria), Esercizio - Spostare i valori di una list in un array dinamico senza delete (quarto appello 2021-22, programmazione).

1) L'operatore =:

affermazione V/F motivo
Confronta due variabili F confronta ==; = assegna
Ha side effect V modifica l'operando di sinistra e dà anche un risultato. Vedi C++ - tipi, variabili, espressioni e dichiarazioniun tipo definisce contenuto, intervallo di valori e operazioni; un oggetto è una regione di memoria con un tipo, il valore è la configurazione di bit letta in base al tipo, una variabile è un oggetto con un nome; inizializzazione (dare il primo valore) e assegnamento (cambiare il valore) sono diversi; le conversioni verso un tipo più capiente sono sicure, quelle verso un tipo meno capiente (narrowing, double->int, int->char) non lo sono e il compilatore le segnala di rado, ma la notazione con graffe {} le vieta; un'espressione calcola un valore da operandi e operatori, un lvalue è ciò che può stare a sinistra di =, constexpr vuole un valore noto in compilazione mentre const solo un valore che non cambia; gli operatori con side effect (++, --, +=) non vanno mischiati con altri usi della stessa variabile nella stessa espressione perché l'ordine di valutazione non è definito; una dichiarazione introduce un nome e un tipo, una definizione fornisce anche l'entità (memoria per una variabile, corpo per una funzione).C++ - tipi, variabili, espressioni e dichiarazioni →
Non può essere applicato a UDT a meno che non sia esplicitamente definito l'apposito overloading F per le classi il compilatore genera l'assegnamento di copia membro a membro
Può avere un lvalue alla sua destra V a = b; con b variabile: si usa come rvalue (il suo valore)

2) Il problema del dangling pointer:

affermazione V/F motivo
Si verifica se la memoria non è stata correttamente rilasciata dopo l'uso F quello è il memory leak
Causa un errore di compilazione F il codice è sintatticamente corretto: l'errore è a run-time (uso di memoria non più valida)
Non comporta uno spreco di risorse computazionali V non spreca memoria (a differenza del leak): è un puntatore che punta a memoria già liberata, e il problema è l'accesso non valido
Si verifica solo in una chiamata a funzione F si verifica ovunque si usi un puntatore dopo delete (o a un oggetto uscito dallo scope). Vedi C++ - allocazione dinamica della memoria con new e deletela memoria dinamica si prende dal free store (heap) con new, che restituisce un puntatore all'oggetto o al primo elemento dell'array (new T, new T[n], con inizializzatori {}), e si restituisce con delete (per new) e delete[] (per new[]); il puntatore non sa quanti elementi ci sono, la memoria non ha nome e non ha controllo dei limiti; in C++ non c'è garbage collector (scelta per efficienza e controllo), quindi un'area che perde ogni riferimento diventa garbage e produce un memory leak, e gli errori tipici sono la doppia cancellazione e il dangling pointer (si mette il puntatore a nullptr dopo delete); il distruttore ~T() è chiamato dal compilatore quando l'oggetto esce dallo scope o con delete e libera le risorse, per cui una classe come vector acquisisce la memoria nel costruttore e la rilascia nel distruttore; new alloca e chiama il costruttore, delete chiama il distruttore e libera; operator[] con due versioni (T& e const) dà accesso in lettura e scrittura al buffer; si alloca dinamicamente solo se serve davvero (memoria grande, dimensione nota a run-time, durata controllata).C++ - allocazione dinamica della memoria con new e delete →

3) int v[4] = {0, 1, 2, 3}; int *p = &v[2]; int *q = p + 2;

affermazione V/F motivo
Causa un errore in esecuzione F q punta a &v[4], un elemento oltre la fine: si può formare, non dereferenziare
Causa un errore in compilazione F aritmetica dei puntatori lecita
L'indirizzo contenuto in q è 2*sizeof(int)+p V p + 2 sposta di 2 elementi, cioè di 2*sizeof(int) byte (l'indirizzo, in byte)
Il valore di q-p è calcolabile ed è valutato come 2*sizeof(int) F q - p vale 2 (elementi), non 8. Vedi C++ - puntatori, array e castun puntatore è una variabile che contiene un indirizzo di memoria e sa il tipo dell'oggetto puntato (T*); si ottiene l'indirizzo con & e si accede al dato con * (dereference, un lvalue), nullptr indica nessun oggetto e due puntatori a tipi diversi sono tipi diversi; void* è un puntatore a memoria grezza (accetta qualsiasi puntatore ma non si dereferenzia) e i cast (static_cast, reinterpret_cast, const_cast) servono per interfacciarsi con hardware e codice non modificabile; il puntatore, a differenza della reference, si può spostare, può essere nullptr e va dereferenziato, e assegnarlo copia l'indirizzo (shallow copy) mentre assegnare una reference copia l'oggetto; un array C è una sequenza contigua di elementi dello stesso tipo, con dimensione costante, senza controllo dei limiti, che passato a una funzione decade in un puntatore al primo elemento; l'aritmetica dei puntatori somma o sottrae interi spostando di n elementi (non di n byte) e la differenza fra puntatori è in elementi; gli errori classici sono nullptr, puntatori non inizializzati, accessi fuori dai limiti e puntatori a oggetti usciti dallo scope.C++ - puntatori, array e cast →

4) Un'enumerazione:

affermazione V/F motivo
È equivalente a una classe, ma tutti i membri sono pubblici F è un tipo con valori simbolici, senza funzioni né dati membro (la descrizione corrisponde alla struct)
Crea uno scope se è definita come enum class V Month::jan
Rende più efficiente l'esecuzione del programma F è un valore intero: nessun vantaggio di prestazioni
Può essere usata per rendere il codice più espressivo V Month::feb invece di 2. Vedi C++ - enumerazioni e overloading degli operatoriun'enumerazione è un tipo definito dall'utente che specifica un insieme di valori simbolici (enumeratori); enum class crea uno scope (Month::jan), non si converte implicitamente in int, e i valori partono da 0 o dal valore del precedente più uno; l'overloading degli operatori permette di definire gli operatori esistenti per tipi definiti dall'utente (almeno un operando UDT, stesso numero di operandi e stessa sintassi) scrivendo una funzione di nome operator+, operator++, operator[], ...; si può fare con una helper function (due argomenti per un operatore binario, il primo può essere di qualunque tipo) o con una funzione membro (un argomento in meno, perché il primo operando è l'oggetto, quindi l'UDT deve stare a sinistra: T + int sì, int + T no); << e >> sono helper function che prendono e restituiscono ostream& e istream&; ++ ha due forme (pre e post, quest'ultima con un int fittizio); gli operatori più utili sono =, ==, !=, <, [], ().C++ - enumerazioni e overloading degli operatori →

5) Una variabile data in input a una funzione che ne modifica il valore:

6) Il move constructor in C++:

affermazione V/F motivo
La notazione && è chiamata anche lvalue reference F è la rvalue reference (& è la lvalue reference)
Gli argomenti di tale costruttore devono per definizione essere const F la sorgente va modificata (svuotata)
È utilizzato per spostare i dati, invalidando la sorgente V
Se definito, viene utilizzato automaticamente dal compilatore quando opportuno V il compilatore sceglie fra copia e spostamento. Vedi C++ - costruttori, inizializzazione, copia e spostamentouna buona interfaccia è completa e minimale, ha i costruttori giusti, supporta o proibisce la copia, usa tipi adeguati, dichiara const ciò che non modifica e libera le risorse nel distruttore; il costruttore di default (senza argomenti) esiste quando c'è un valore iniziale sensato, si ottiene con valori di default nei membri (in-class initializer) o argomenti di default, e explicit vieta la conversione implicita da un argomento; initializer_list permette la lista {a, b, c} e con le graffe ha la precedenza sul costruttore a un intero (vector{3} è un elemento, vector(3) sono tre); la copia di default è membro a membro (shallow copy) e con un puntatore a un buffer duplica il puntatore invece del contenuto (due oggetti che condividono i dati e una doppia delete), quindi servono il costruttore di copia e l'assegnamento di copia che fanno la deep copy (l'assegnamento prima alloca e copia, poi libera i vecchi dati, e deve tollerare l'auto-assegnamento); this è il puntatore all'oggetto corrente, parametro implicito delle funzioni membro; il costruttore e l'assegnamento di spostamento (T&&, rvalue reference) rubano il buffer della sorgente lasciandola vuota ma valida, il compilatore li sceglie da solo (con la copy elision a volte non servono nemmeno); i cinque (distruttore, copia, spostamento) vanno definiti insieme.C++ - costruttori, inizializzazione, copia e spostamento →

7) La classe STL std::vector:

affermazione V/F motivo
L'operatore [] e la funzione membro at() permettono entrambi di accedere in lettura ai dati V
L'operatore [] compie un check sulla dimensione e lancia un'eccezione se si accede fuori range F [] non controlla (comportamento indefinito)
La funzione membro at() compie un check sulla dimensione e lancia un'eccezione se si accede fuori range V std::out_of_range
L'operatore [] può essere utilizzato anche per accedere in scrittura al vettore V restituisce una reference all'elemento. Vedi STL - sequenze, iteratori e container (vector, list, string)La STL (Standard Template Library) fornisce contenitori (vector, list, map, ...) e algoritmi generici (sort, find, accumulate, ...) tenuti insieme da una interfaccia unificata: la sequenza, un intervallo [begin, end) di elementi gestito tramite iteratori (oggetti che identificano un elemento: * dereferenzia, ++ avanza, == e != confrontano; end è il primo elemento dopo l'ultimo, quindi una sequenza vuota ha begin == end). Gli algoritmi conoscono solo gli iteratori e i contenitori espongono solo iteratori, così i due mondi sono disaccoppiati e cambiare contenitore non cambia l'algoritmo. Un iteratore di lista è una classe annidata con un puntatore al nodo; auto deduce il tipo dell'iteratore. vector (contiguo, [] e at(), insert/erase costosi e che invalidano gli iteratori), string (un vector di char con + e +=), list (nodi concatenati, insert/erase senza spostare gli altri elementi, niente []) e char[] (array C: dimensione fissa, niente iteratori, == confronta i puntatori) a confronto.STL - sequenze, iteratori e container (vector, list, string) →

8) Il distruttore di una classe in C++:

affermazione V/F motivo
Viene chiamato quando l'oggetto esce dallo scope V
Viene chiamato quando si usa l'operatore delete V delete invoca il distruttore e poi libera la memoria
Se non definito, viene implementato automaticamente dal compilatore V e distrugge i membri
Viene chiamato quando si usa il move constructor della classe F il costruttore di spostamento non distrugge nulla; il distruttore della sorgente (svuotata) sarà chiamato più tardi, quando esce dallo scope

9) Le include guards in C++:

10) La compilazione di un progetto C++:

affermazione V/F motivo
Il linker segnala gli errori di sintassi F li segnala il compilatore
Un errore di linking si verifica quando una parte del software esterna al progetto (ad esempio una libreria) è mancante V undefined reference
I commenti presenti nel codice vengono inseriti nel programma compilato F il commento è per le persone: sparisce nella compilazione
Il compilatore gcc/g++ segnala le conversioni non valide fra tipi diversi V errori di tipo sì; le conversioni "non sicure" (narrowing) accettate dal C++ non sono errori, ma {} le segnala. Vedi C++ - introduzione, compilazione e linkingC++ è un linguaggio compilato e standard che dà controllo totale sulla macchina (basso livello) e strumenti di astrazione (alto livello); un programma parte da main, usa gli stream (std::cout << ...) e le direttive del preprocessore (#include); il passaggio da sorgente a eseguibile ha tre fasi, preprocessore (espande #include e macro), compilatore (controlla sintassi e tipi e produce un file oggetto in codice macchina) e linker (unisce i file oggetto e le librerie risolvendo i nomi); gli errori di sintassi e di tipo sono del compilatore, i simboli dichiarati ma non definiti (undefined reference) sono del linker; si compila da riga di comando con g++ -o eseguibile sorgente.cpp -lLibreria.C++ - introduzione, compilazione e linking →

Versione ripasso

Teoria collegata