[RISOLTO] Primi passi C

C, C++, Java, ...

[RISOLTO] Primi passi C

Messaggioda elatorre » 08/06/2020, 14:55

Salve.
Non riesco a capire dove sbaglio.
Codice: Seleziona tutto
#include<stdio.h>
#include<stdlib.h>

int main (void)
{
   /* Variables. */
   int lower, upper, step;
   float fah, cel;
   
   /* Set initials conditions values. */
   lower = 0;
   upper = 300;
   step = 20;
   fah = 0.;
   cel = 0.;

   while (lower <= upper)
   {
      
      cel=celsius(fah);
      printf("%3.0f\t%6.1f\n",fah,cel);   
      fah = fah + (1.*step);
      lower = lower + step;
   }

   exit(0);
}

float celsius (float fahreneit)
{   
   float answer;
   answer = (5.0/9.0)*(fahreneit-32);
   return answer;
}


OUTPUT A SCHERMO
=================
0 1.0
20 1.0
40 1.0
60 1.0
80 1.0
100 1.0
120 1.0
140 1.0
160 1.0
180 1.0
200 1.0
220 1.0
240 1.0
260 1.0
280 1.0
300 1.0

Perché il risultato è sempre 1.0?

Grazie per il supporto,

Emanuele.
elatorre
Newbie
Newbie
 
Messaggi: 15
Iscritto il: 13/03/2015, 12:28
Località: Pomezia (ROMA)

Re: [RISOLTO] Primi passi C

Messaggioda elatorre » 08/06/2020, 15:21

Mancava la dicitura:

Codice: Seleziona tutto
extern float celsius(float fah);


Adesso la funzione è visibile.

:)
elatorre
Newbie
Newbie
 
Messaggi: 15
Iscritto il: 13/03/2015, 12:28
Località: Pomezia (ROMA)

Re: [RISOLTO] Primi passi C

Messaggioda Aki » 08/06/2020, 21:23

Ciao,

Grazie per aver posto un quesito così interessate e aggiornato la discussione.

Credo, però, che tu sia arrivato ad una soluzione che potrebbe essere fuorviante, se non opportunamente esplicitata, per un lettore poco avvezzo al linguaggio C: hai capito perché ottieni il risultato desiderato e perché prima, al contrario, non lo ottenevi ?

Come indizio, per gli altri frequentatori del forum, riporto l'output del compilatore gcc (che non compila il codice sorgente) quando si prova a compilare il programma che hai indicato nel primo messaggio della discussione:
Codice: Seleziona tutto
$ gcc prova.c -o prova
prova.c: In function ‘main’:
prova.c:20:11: warning: implicit declaration of function ‘celsius’ [-Wimplicit-function-declaration]
       cel=celsius(fah);
           ^~~~~~~
prova.c: At top level:
prova.c:29:7: error: conflicting types for ‘celsius’
 float celsius (float fahreneit)
       ^~~~~~~
prova.c:20:11: note: previous implicit declaration of ‘celsius’ was here
       cel=celsius(fah);
           ^~~~~~~

Con quale compilatore lo hai compilato generando l'output che hai inviato nel primo messaggio della discussione ?
Facci sapere !
Aki
Global Moderator
Global Moderator
 
Messaggi: 9442
Iscritto il: 27/12/2007, 16:59

Re: [RISOLTO] Primi passi C

Messaggioda TonyB » 09/06/2020, 21:50

elatorre ha scritto:Mancava la dicitura:

Codice: Seleziona tutto
extern float celsius(float fah);


Adesso la funzione è visibile.

:)


Ciao,
adesso la funzione é' visibile perché ne hai aggiunto, con quella riga, il prototipo.
Il compilatore deve conoscere il tipo di ritorno ed i parametri di una funzione *prima* del suo utilizzo, per verificare la correttezza formale, per questo il prototipo va messo prima del main().
Il qualificatore extern dovresti toglierlo, indica che quella funzione è definita in un altro modulo e non è questo il caso.
I nomi delle variabili nella lista dei parametri formali del prototipo puoi anche evitarli, basta indicarne solo il tipo:
Codice: Seleziona tutto
float celsius(float );

è sufficiente.
Avatar utente
TonyB
Sr. Member
Sr. Member
 
Messaggi: 381
Iscritto il: 15/12/2007, 20:57

Re: [RISOLTO] Primi passi C

Messaggioda TonyB » 10/06/2020, 8:29

Oops, mi accorgo solo ora dell'intento educativo del post di Aki, che purtroppo ho vanificato con il mio intervento. Sorry.
Avatar utente
TonyB
Sr. Member
Sr. Member
 
Messaggi: 381
Iscritto il: 15/12/2007, 20:57

Re: [RISOLTO] Primi passi C

Messaggioda Aki » 10/06/2020, 19:11

TonyB ha scritto:Oops, mi accorgo solo ora dell'intento educativo del post di Aki, che purtroppo ho vanificato con il mio intervento. Sorry.

Figurati, ve bene anche così. Sarebbe, comunque, interessante un riscontro anche da elatorre
Aki
Global Moderator
Global Moderator
 
Messaggi: 9442
Iscritto il: 27/12/2007, 16:59

Re: [RISOLTO] Primi passi C

Messaggioda elatorre » 11/06/2020, 11:22

Scusate il ritardo.
Dunque, il compilatore impiegato è [gcc version 8.3.0 (Debian 8.3.0-6)].
La funzione [celsius] in realtà è collocata in altro file separato (immaginando lo scenario di poter riutilizzare le funzioni in altri programmi) all'interno del sorgente [physic.c]. Per quanto minimale, stavo già cercando un approccio più rigoroso.
Detto ciò, il tutto viene gestito dal Makefile (omesso nel mio post) che riporto di seguito:
Codice: Seleziona tutto
# author       : E. M. Latorre
# start encode : 06.05.2020
# last encode  : 06.08.2020

# Variables.
CFLAGS=-ansi -Wpedantic
TARGET=fahcel.x
COMPILER=gcc
OBJECTS=fahcel.o lib/physic.o

all:   ${TARGET}

fahcel.x:   fahcel.o lib/physic.o
      ${COMPILER} ${CFLAGS} -o ${TARGET} ${OBJECTS};

fahcel.o:   fahcel.c
      ${COMPILER} ${CFLAGS} -c fahcel.c

lib/physic.o:   lib/physic.c
      ${COMPILER} ${CFLAGS} -c lib/physic.c
      mv physic.o lib/
.PHONY:      clean

clean:
      -rm ${TARGET} *.o *~ */*.o;
      clear;
      ls -l --color;

Per completezza, sto seguendo il testo Darnell/Margolis "C Manuale di programamzione".

In conclusione quindi, se ho ben compreso, la direttiva "extern", non è necessaria.

Grazie per il supporto.

Emanuele.

P.S.
A margine della discussione, qual è la soluzione (o meglio, dove sbaglio :) ) per evitare
Codice: Seleziona tutto
mv physic.o lib/

nel Makefile?
Non trovando l'object nella directory [lib] la compilazione non viene portata a termine attraverso Makefile. Indipendentemente dalla posizione del sorgente, l'object viene generato nella root del progetto, motivo per cui sono costretto a spostarlo nella directory [lib] per dare seguito al Makefile.
elatorre
Newbie
Newbie
 
Messaggi: 15
Iscritto il: 13/03/2015, 12:28
Località: Pomezia (ROMA)

Re: [RISOLTO] Primi passi C

Messaggioda TonyB » 12/06/2020, 22:36

elatorre ha scritto:A margine della discussione, qual è la soluzione (o meglio, dove sbaglio :) ) per evitare
Codice: Seleziona tutto
mv physic.o lib/

nel Makefile?
Non trovando l'object nella directory [lib] la compilazione non viene portata a termine attraverso Makefile. Indipendentemente dalla posizione del sorgente, l'object viene generato nella root del progetto, motivo per cui sono costretto a spostarlo nella directory [lib] per dare seguito al Makefile.


Ciao, hai più possibilità.
La più semplice è cambiare il nome del file oggetto inserendo il percorso davanti; la regola di lib/physic.o diventa quindi:
Codice: Seleziona tutto
lib/physic.o:   lib/physic.c
      ${COMPILER} ${CFLAGS} -c lib/physic.c -o lib/physic.o


Oppure si può entrare nella directory lib e dare il comando da lì:
Codice: Seleziona tutto
lib/physic.o:   lib/physic.c
      cd lib/
      ${COMPILER} ${CFLAGS} -c ../physic.c

fai attenzione ai tab ad inizio riga.
Avatar utente
TonyB
Sr. Member
Sr. Member
 
Messaggi: 381
Iscritto il: 15/12/2007, 20:57

Re: [RISOLTO] Primi passi C

Messaggioda Aki » 14/06/2020, 8:56

elatorre ha scritto:In conclusione quindi, se ho ben compreso, la direttiva "extern", non è necessaria.

Sì, ma non è questo la causa del comportamento inatteso della funzione (di cui hai riferito nel primo messaggio).

La cosa importante è che senza dichiarazione del prototipo della funzione celsius() prima della definizione della funzione stessa otteni un output completamente differente: hai capito perché in assenza del prototipo della funzione ottieni codice compilato, ma output diverso da quello desiderato ?

Un'altra domanda, se possibile: il compilatore C che hai usato, ti ha segnalato warning in fase di compilazione rispetto al codice generato ?
Aki
Global Moderator
Global Moderator
 
Messaggi: 9442
Iscritto il: 27/12/2007, 16:59

Re: [RISOLTO] Primi passi C

Messaggioda elatorre » 15/06/2020, 10:31

Grazie TonyB per il supporto; preferisco la prima soluzione :)
Ora il Makefile è il seguente:
Codice: Seleziona tutto
# author       : E. M. Latorre
# start encode : 06.05.2020
# last encode  : 06.08.2020

# Variables.
CFLAGS=-ansi -Wpedantic
TARGET=fahcel.x
COMPILER=gcc
OBJECTS=fahcel.o lib/physic.o

all:   ${TARGET}

fahcel.x:   fahcel.o lib/physic.o
      ${COMPILER} ${CFLAGS} -o ${TARGET} ${OBJECTS};

fahcel.o:   fahcel.c
      ${COMPILER} ${CFLAGS} -c fahcel.c

lib/physic.o:   lib/physic.c
      ${COMPILER} ${CFLAGS} -c lib/physic.c -o lib/physic.o

.PHONY:      clean

clean:
      -rm ${TARGET} *.o *~ */*.o;
      clear;
      ls -l --color;


Per Aki: il compilatore non segnala <warning> quando non dichiaro il prototipo (vedere screen alleagato).
Motivo per cui, non comprendo tutt'ora il motivo del codice di output [1.0]. Ho provato ad immaginare che, in assenza di dichiarazione di prototipo:
Codice: Seleziona tutto
cel=celsius(fah);

fah venga interpretato come [0.], tuttavia, il calcolo non dovrebbe essere [1.0]:
Codice: Seleziona tutto
cels = (5.0/9.0)*(fahr-32.0);

Sinceramente non mi viene in mente altro, a questo punto di conoscenza del C.
Ho pensato al debug per interrogare la variabile al momento della chiamata della funzione, tuttavia al momento, ho poca dimestichezza per utilizzarlo su qualche IDE (Anjuta o similari).
A questo punto, mi piacerebbe ricevere la tua spiegazione :)
Allegati
fahcel.PNG
fahcel.PNG (21.25 KiB) Osservato 873 volte
elatorre
Newbie
Newbie
 
Messaggi: 15
Iscritto il: 13/03/2015, 12:28
Località: Pomezia (ROMA)

Re: [RISOLTO] Primi passi C

Messaggioda TonyB » 15/06/2020, 12:03

Se vuoi una risposta completa è necessario che posti per bene il contenuto di tutti i file sorgente, perché con il file fahcel.c originario da te postato a me non compila, anche creando il file lib/physic.c. Con la versione finale invece, gcc, anzi il collegatore, si lamenta per una definizione multipla della funzione celsius(), una volta in fahcel.c ed un'altra in lib/physic.c. Non capisco come riesca a compilare sulla tua macchina.
Avatar utente
TonyB
Sr. Member
Sr. Member
 
Messaggi: 381
Iscritto il: 15/12/2007, 20:57

Re: [RISOLTO] Primi passi C

Messaggioda elatorre » 15/06/2020, 13:21

Benissimo.
Ecco le versioni finali di [fahcel.c] e [lib/physic.c]
Codice: Seleziona tutto
/*
 * author       : E. M. Latorre
* start encode : 06.11.2020
 * last encode  : 06.11.2020
 * name         : fahcel.c
 * target       : Compute an interval of temperatures between Fahreneit/Celsius degrees.
*/

#include<stdio.h>
#include<stdlib.h>

int main (void)
{
   /* Variables. */
   int lower, upper, step;
   float fah, cel;
   
   /* Functions. */
   /* extern float celsius (float fah); Commentata volutamente per output [1.0]???*/

   /* Set initials conditions values. */
   lower = 0;
   upper = 300;
   step = 20;
   fah = 0.;

   /* Format results. */
   printf("==============\n");
   printf("T[°F]    T[°C]\n");
   printf("==============\n");
   /* Call [celsius] function and calculate Celsius temperature. */
   while (lower <= upper)
   {
      cel=celsius(fah);
      printf("%3d\t%6.1f\n",lower,cel);   
      lower=lower+step;
      fah=fah+(float)step;
   }

   exit(0);
}


Codice: Seleziona tutto
/* ===================================================================================== */
/*
 * author       : E. M. Latorre
 * start encode : 06.11.2020
 * last encode  : 06.11.2020
 * name      : celsius
 * target       : Compute Celsius temperature.
*/

float celsius (float fahr)
{   
   /* Variables. */
   float cels;

   /* Set initials conditions values. */
   cels = 0.;

   cels = (5.0/9.0)*(fahr-32.0);

   return cels;
}

elatorre
Newbie
Newbie
 
Messaggi: 15
Iscritto il: 13/03/2015, 12:28
Località: Pomezia (ROMA)

Re: [RISOLTO] Primi passi C

Messaggioda TonyB » 15/06/2020, 17:34

Allora, sono riuscito a riprodurre l'errore.
Quando manca il prototipo della funzione celsius() in fahcel.c, i file oggetto vengono creati da gcc -c, ma il linker, invocato dall'ultima esecuzione di gcc -o, non riesce a risolvere correttamente i simboli. Il problema è che non vengono lanciati warning, o meglio, il warning della definizione implicita di celsius() viene lanciato se eliminiamo il flag -ansi.
Quando gcc -c compila fahcel.c trova la funzione celsius() che non è definita nel file. Nella tabella dei simboli lascia allora un campo indefinito, che verrà gestito dal linker. Ad esso spetterà il compito di risolvere questo simbolo, ovvero trovare in quale modulo si trova celsius() e riempire il campo lasciato vuoto da gcc. Purtroppo ld, il linker, non fa miracoli, per questo dobbiamo "aiutarlo" inserendo i prototipi di funzioni o, meglio ancora, i file di header. Nel nostro caso ld trova la funzione celsius() nel modulo lib/physisc.o, la esegue, ma non gli passa correttamente i parametri. Il valore di ritorno di questa funzione, poi, è assolutamente casuale, come possiamo vedere modificando celsius() ed inserendo dei messaggi di check:
Codice: Seleziona tutto
#include<stdio.h>
float celsius(float fahreneit) {
        printf("siamo nella funzione celsius! ");
        float answer = (5.0/9.0)*(fahreneit-32);
        printf("F= %3.3f C= %3.3f\n",fahreneit, answer);
        return answer;
}

che da questo output:
Codice: Seleziona tutto
tony@ThinkPad-X230:~/archivio/applicazioni/programming/latorre_question$ ./fahcel.x
siamo nella funzione celsius! F= 0.000 C= -17.778
  0     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
 20     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
 40     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
 60     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
 80     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
100     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
120     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
140     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
160     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
180     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
200     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
220     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
240     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
260     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
280     20.000
siamo nella funzione celsius! F= 0.000 C= -17.778
300     20.000

le variabili automatiche in celsius() prendono i valori corretti (zero per la var fahrneite non inizializzata e celsius) ma il valore di ritorno è assolutamente diverso, adesso è addirittura 20.
Avatar utente
TonyB
Sr. Member
Sr. Member
 
Messaggi: 381
Iscritto il: 15/12/2007, 20:57

Re: [RISOLTO] Primi passi C

Messaggioda elatorre » 16/06/2020, 12:09

Grazie TonyB, finalmente è chiaro. :)

Beh, devo studiare tanto; il C mi risulta abbastanza ostico in questa fase.

Tuttavia, mi sento fiducioso; qui s'impara molto di più che sui testi.

;D
elatorre
Newbie
Newbie
 
Messaggi: 15
Iscritto il: 13/03/2015, 12:28
Località: Pomezia (ROMA)

Re: [RISOLTO] Primi passi C

Messaggioda Aki » 28/06/2020, 8:17

TonyB ha scritto:Quando gcc -c compila fahcel.c trova la funzione celsius() che non è definita nel file. Nella tabella dei simboli lascia allora un campo indefinito, che verrà gestito dal linker. [..] Il valore di ritorno di questa funzione, poi, è assolutamente casuale[..]

Argomento molto interessante. Può essere utile approfondire sul valore di ritorno della funzione in caso di assenza di prototipo di funzione o di assenza di definizione di funzione prima della chiamata alla medesima.

In assenza del prototipo della funzione (o della sua definizione), il compilatore genera una dichiarazione "implicita" di prototipo, come di seguito indicato (nell'esempio, per una funzione chiamata funct):
Codice: Seleziona tutto
int funct()

Genera, cioè, il prototipo di una funzione che restituisce un valore di ritorno di tipo 'int' e che può avere un numero arbitrario (e non specificato) di argomenti.

Ad esempio, nel caso della funzione celsius(), in assenza di prototipo, è generato il prototipo di funzione implicita:
Codice: Seleziona tutto
int celsius()

Puoi fare la prova dichiarando in fahcel.c il prototipo int celsius() e, dopo la compilazione, otterrai esattamente lo stesso output (errato dal punto di vista funzionale) per il quale hai aperto la discussione.

Ma perché in assenza di prototipo è generato un output errato in questa condizione ?

La funzione celsius(), come da te scritta, è compilata in codice macchina dal compilatore per restituire un dato di tipo "float", come da codice sorgente:
Codice: Seleziona tutto
float celsius (float fahreneit)
{   
   float answer;
   answer = (5.0/9.0)*(fahreneit-32);
   return answer;
}

Con la dichiarazione implicita (cioè in assenza di prototipo), la funzione che a sua volta invoca celsius() è compilata in codice macchina per ricevere un valore di tipo int.

I due tipi (float ed int) sono diversi a livello binario e possono anche essere restituiti con diverse modalità a seconda della "calling convention" adottata [0] (a livello di codice macchina; sysv nel caso di Linux) cioè delle regole che segue il compilatore per generare il codice macchina da eseguire che gestisce il passaggio dei dati tra funzione chiamante e funzione chiamata.

In questa condizione, il compilatore non può garantire (a livello binario del tipo di dato) che la restituzione del valore dalla funzione chiamata (celsius) alla chiamante (main) avvenga come atteso dalla funzione chiamata (celsius). Almeno questo avviene se la compilazione è effettuata impostando il compilatore allo standard C89 che corrisponde allo standard ANSI (dovrebbe essere generato un warning). Per gli standard successivi, questa condizione genera un errore di compilazione su cui deve necessariamente intervenire il programmatore.

Riporto di seguito il codice macchina generato dal compilatore gcc (per architettura x86_64 con estensione SSE2) in caso dichiarazione implicita (la chiamata vera e propria della funzione celsius() avviene con l'istruzione 'callq', le istruzioni precedenti sono di preparazione alla chiamata e le successive per la gestione del dato restituito in ritorno):
Codice: Seleziona tutto
objdump -S  ./fahcel.x
[..]
   /* Call [celsius] function and calculate Celsius temperature. */
   while (lower <= upper)
    119f:       eb 4c                   jmp    11ed <main+0x98>
   {
      cel = celsius(fah);
    11a1:       f3 0f 5a 45 f8          cvtss2sd -0x8(%rbp),%xmm0
    11a6:       b8 01 00 00 00          mov    $0x1,%eax
    11ab:       e8 4f 00 00 00          callq  11ff <celsius>
    11b0:       f3 0f 2a c0             cvtsi2ss %eax,%xmm0
    11b4:       f3 0f 11 45 ec          movss  %xmm0,-0x14(%rbp)
[..]

Al contrario, se il file fahcel.c contiene il prototipo della funzione float celsius(float), è generato quest'altro codice macchina:
Codice: Seleziona tutto
   /* Call [celsius] function and calculate Celsius temperature. */
   while (lower <= upper)
    119f:       eb 45                   jmp    11e6 <main+0x91>
   {
      cel = celsius(fah);
    11a1:       f3 0f 10 45 f8          movss  -0x8(%rbp),%xmm0
    11a6:       e8 4d 00 00 00          callq  11f8 <celsius>
    11ab:       66 0f 7e c0             movd   %xmm0,%eax
    11af:       89 45 ec                mov    %eax,-0x14(%rbp)
[..]


[0] https://wiki.osdev.org/System_V_ABI
Aki
Global Moderator
Global Moderator
 
Messaggi: 9442
Iscritto il: 27/12/2007, 16:59


Torna a Programmazione

Chi c’è in linea

Visitano il forum: Nessuno e 1 ospite

cron