next up previous
Next: Architettura Up: FAQ di Calcolatori Elettronici Previous: Autocorrezione

Assembler

Q 3.1   Quando provo a compilare, mi compare il seguente errore:
c:/djgpp/tmp/cbaaaaa(.text+0xac):fake: undefined reference to 'xxx'
Cosa vuol dire?

È un errore del linker: dice che non riesce a trovare la definizione dell'etichetta xxx. Succede, normalmente, se:
  1. avete dimenticato il simbolo % davanti al nome di qualche registro (ve ne accorgete perché xxx è proprio il nome del registro in questione). L'assemblatore ha interpretato il nome del registro come una etichetta di cui, poi, il collegatore non ha trovato la definizione;
  2. (più frequentemente) avete sbagliato la traduzione del nome di qualche funzione da C++ ad assembler, o non l'avete resa ``global''. Ricontrollate l'etichetta all'inizio della funzione, controllate di aver scritto la direttiva .global e che le due etichette coincidano; controllate infine le istruzioni call.

Q 3.2   Quando provo a compilare, mi compare il seguente errore:
xxx.s:Assembler messages:
xxx.s:0: Warning: end of file not at the end of a line; new line inserted
Cosa vuol dire?

Il file da assemblare deve terminare con un a capo. Se così non è, state attenti, perchè l'ultima riga del file (quella senza l'a capo finale) viene ignorata. Normalmente, quella riga conteneva una ret: se è così, il vostro programma, invece di tornare al chiamante, ``tira dritto'' e, generalmente, l'effetto è quello descritto nella domanda successiva.

Q 3.3   Quando eseguo un programma, ottengo il seguente output:
Exiting due to signal SIGSEGV
Stack fault at eip xxxxxxxx
e poi il contenuto in esadecimale di tutti i registri. Come posso individuare l'errore?

Il programma è stato interrotto per via di un accesso non consentito alla memoria e quella è la stampa dello stato del processore al momento dell'accesso scorretto. Ovviamente le possibili cause dell'errore sono molteplici, anche se, dai vostri compiti, ho visto che le più frequenti sono: Ovviamente la lista non è completa, ma vi consiglio, per prima cosa, di controllare questi possibili errori. Indipendentemente da questo, un aiuto può essere dato dal sapere qual è l'istruzione che ha causato l'errore. Per far questo basta guardare il valore di %eip al momento dell'errore (quello mostrato nell'output). Usate poi il seguente comando:
objdump --disassemble a.exe > dis.txt
(al posto di a.exe scrivete il nome dell'eseguibile che vi da errore). In questo modo otteniamo il file dis.txt che contiene il disassemblato dell'eseguibile con, nella prima colonna, l'indirizzo di ogni istruzione. Qui potete cercare l'istruzione che si trova all'indirizzo che era in %eip quando si è verificato l'errore.

Purtroppo, anche se l'accesso scorretto è stato causato al 99.9999% da un errore nel vostro programma (e non in Windows, o in DJGPP, o ...), l'effetto dell'errore potrebbe non essersi manifestato subito, ma potrebbe aver causato un errore molto dopo, magari durante l'invocazione di una funzione di libreria. In questo caso, l'istruzione che corrisponde ad %eip potrebbe non essere una tra quelle che avete scritto voi, oppure non essere proprio all'interno dell'eseguibile. In questo caso la faccenda è molto più complicata e, senza un debugger, non resta che ispezionare meglio ciò che avete scritto.

Q 3.4   Come mai, quando è stato ridefinito il costruttore di copia di una classe A, gcc passa per riferimento anche i parametri di tipo A dichiarati per valore?

Immaginate di avere una classe A e le seguenti funzioni
void f(A) {}    // oggetto di classe A passato per valore
A g() { A a; return a; } //oggetto di classe A restituito
e supponiamo di dover tradurre l'espressione:
f(g());
la chiamata a g deve essere eseguita prima di quella a f, e bisogna passare a g l'indirizzo a cui scrivere l'oggetto di classe A da lei restituito. Sarebbe complicato impostare le cose in modo che g lo vada a scrivere direttamente nella posizione dello stack in cui poi lo vuole f (bisognerebbe cominciare a preparare il frame di f prima di quello di g, che però va chiamata per prima), quindi si preferisce allocare un oggetto temporaneo (una variabile locale, non dichiarata esplicitamente dal programmatore) e passare l'indirizzo di tale variabile a g. Terminata la chiamata a g, bisogna costruire il frame di attivazione di f.

Ora: se non è stato ridefinito il costruttore di copia, basta copiare bit a bit la variabile temporanea nel posto giusto del frame di f. Ma se è stato ridefinito il costruttore di copia? evidentemente il programmatore ha ritenuto che la copia bit a bit, per questa classe, non ha senso, quindi non possiamo prenderci la libertà di farla, in generale. Ma nemmeno possiamo chiamare il costruttore di copia, perchè la necessità di questa copia è nata da una nostra esigenza implementativa, e non era nella semantica del linguaggio. Da qui il comportamento di gcc: se il costruttore di copia è stato ridefinito, la copia non viene fatta, e l'oggetto viene passato a f per riferimento.


next up previous
Next: Architettura Up: FAQ di Calcolatori Elettronici Previous: Autocorrezione
2012-09-14