Ricerca…


Osservazioni

La dichiarazione di identificatore riferita ad oggetto o funzione viene spesso definita in breve come semplice dichiarazione di oggetto o funzione.

Chiamare una funzione da un altro file C.

foo.h

#ifndef FOO_DOT_H    /* This is an "include guard" */
#define FOO_DOT_H    /* prevents the file from being included twice. */
                     /* Including a header file twice causes all kinds */
                     /* of interesting problems.*/

/**
 * This is a function declaration.
 * It tells the compiler that the function exists somewhere.
 */
void foo(int id, char *name);

#endif /* FOO_DOT_H */

foo.c

#include "foo.h"    /* Always include the header file that declares something
                     * in the C file that defines it. This makes sure that the
                     * declaration and definition are always in-sync.  Put this
                     * header first in foo.c to ensure the header is self-contained.
                     */
#include <stdio.h>
                       
/**
 * This is the function definition.
 * It is the actual body of the function which was declared elsewhere.
 */
void foo(int id, char *name)
{
    fprintf(stderr, "foo(%d, \"%s\");\n", id, name);
    /* This will print how foo was called to stderr - standard error.
     * e.g., foo(42, "Hi!") will print `foo(42, "Hi!")`
     */
}

main.c

#include "foo.h"

int main(void)
{
    foo(42, "bar");
    return 0;
}

Compila e collega

Per prima cosa, compiliamo sia foo.c sia main.c sui file oggetto . Qui usiamo il compilatore gcc , il tuo compilatore può avere un nome diverso e ha bisogno di altre opzioni.

$ gcc -Wall -c foo.c
$ gcc -Wall -c main.c

Ora li colleghiamo insieme per produrre il nostro eseguibile finale:

$ gcc -o testprogram foo.o main.o

Utilizzando una variabile globale

L'uso di variabili globali è generalmente scoraggiato. Rende il tuo programma più difficile da capire e più difficile da eseguire il debug. Ma a volte l'uso di una variabile globale è accettabile.

global.h

#ifndef GLOBAL_DOT_H    /* This is an "include guard" */
#define GLOBAL_DOT_H

/**
 * This tells the compiler that g_myglobal exists somewhere.
 * Without "extern", this would create a new variable named
 * g_myglobal in _every file_ that included it. Don't miss this!
 */
extern int g_myglobal; /* _Declare_ g_myglobal, that is promise it will be _defined_ by
                        * some module. */

#endif /* GLOBAL_DOT_H */

global.c

#include "global.h" /* Always include the header file that declares something
                     * in the C file that defines it. This makes sure that the
                     * declaration and definition are always in-sync.
                     */
                       
int g_myglobal;     /* _Define_ my_global. As living in global scope it gets initialised to 0 
                     * on program start-up. */

main.c

#include "global.h"

int main(void)
{
    g_myglobal = 42;
    return 0;
}

Vedi anche Come utilizzare extern per condividere variabili tra file sorgente?

Utilizzo di costanti globali

Le intestazioni possono essere utilizzate per dichiarare risorse di sola lettura utilizzate globalmente, ad esempio le tabelle di stringhe.

Dichiara quelli in un'intestazione separata che viene inclusa da qualsiasi file (" Unità di traduzione ") che vuole farne uso. È utile utilizzare la stessa intestazione per dichiarare un'enumerazione correlata per identificare tutte le risorse stringa:

resources.h:

#ifndef RESOURCES_H
#define RESOURCES_H

typedef enum { /* Define a type describing the possible valid resource IDs. */
  RESOURCE_UNDEFINED = -1, /* To be used to initialise any EnumResourceID typed variable to be 
                              marked as "not in use", "not in list", "undefined", wtf.
                              Will say un-initialised on application level, not on language level. Initialised uninitialised, so to say ;-)
                              Its like NULL for pointers ;-)*/
  RESOURCE_UNKNOWN = 0,    /* To be used if the application uses some resource ID, 
                              for which we do not have a table entry defined, a fall back in 
                              case we _need_ to display something, but do not find anything 
                              appropriate. */

  /* The following identify the resources we have defined: */
  RESOURCE_OK,
  RESOURCE_CANCEL,
  RESOURCE_ABORT,
  /* Insert more here. */

  RESOURCE_MAX /* The maximum number of resources defined. */
} EnumResourceID;


extern const char * const resources[RESOURCE_MAX]; /* Declare, promise to anybody who includes 
                                      this, that at linkage-time this symbol will be around. 
                                      The 1st const guarantees the strings will not change, 
                                      the 2nd const guarantees the string-table entries 
                                      will never suddenly point somewhere else as set during 
                                      initialisation. */
#endif

Per definire effettivamente le risorse create un file .c correlato, vale a dire un'altra unità di traduzione che contiene le istanze effettive di ciò che è stato dichiarato nel relativo file di intestazione (.h):

resources.c:

#include "resources.h" /* To make sure clashes between declaration and definition are
                          recognised by the compiler include the declaring header into
                          the implementing, defining translation unit (.c file).

/* Define the resources. Keep the promise made in resources.h. */
const char * const resources[RESOURCE_MAX] = {
  "<unknown>",
  "OK",
  "Cancel",
  "Abort"
};

Un programma che usa questo potrebbe assomigliare a questo:

main.c:

#include <stdlib.h> /* for EXIT_SUCCESS */
#include <stdio.h>

#include "resources.h"


int main(void)
{
  EnumResourceID resource_id = RESOURCE_UNDEFINED;

  while ((++resource_id) < RESOURCE_MAX)
  {
    printf("resource ID: %d, resource: '%s'\n", resource_id, resources[resource_id]);
  }

  return EXIT_SUCCESS;
}

Compilare i tre file sopra usando GCC e collegarli per diventare il file main del programma, per esempio usando questo:

gcc -Wall -Wextra -pedantic -Wconversion -g  main.c resources.c -o main

(usa questi -Wall -Wextra -pedantic -Wconversion per rendere il compilatore davvero pignolo, in modo da non perdere nulla prima di postare il codice in SO, dirà il mondo, o anche se vale la pena di distribuirlo in produzione)

Esegui il programma creato:

$ ./main

E prendi:

resource ID: 0, resource: '<unknown>'
resource ID: 1, resource: 'OK'
resource ID: 2, resource: 'Cancel'
resource ID: 3, resource: 'Abort'

introduzione

Esempi di dichiarazioni sono:

int a; /* declaring single identifier of type int */

La suddetta dichiarazione dichiara un identificatore singolo denominato a che si riferisce ad un oggetto con tipo int .

int a1, b1; /* declaring 2 identifiers of type int */

La seconda dichiarazione dichiara 2 identificatori denominati a1 e b1 che si riferiscono ad alcuni altri oggetti con lo stesso tipo int .

Fondamentalmente, il modo in cui funziona è come questo: prima si inserisce un tipo , quindi si scrive una singola o più espressioni separate da una virgola ( , ) ( che non verrà valutata a questo punto) e che dovrebbero altrimenti essere definite come dichiaratori in questo contesto ). Nella scrittura di tali espressioni, è consentito applicare solo gli operatori di riferimento indiretto ( * ), di chiamata di funzione ( ( ) ) o di indice (o di indicizzazione di matrice - [ ] ) su un identificatore (non è possibile utilizzare alcun operatore). L'identificatore utilizzato non è richiesto per essere visibile nell'ambito corrente. Qualche esempio:

/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# Descrizione
1 Il nome del tipo intero.
2 Espressione non valutata che applica indiretta a qualche identificatore z .
3 Abbiamo una virgola che indica che seguirà un'altra espressione nella stessa dichiarazione.
4 Espressione non valutata che applica indiretta ad un altro identificatore x .
5 Espressione non valutata che applica indiretta al valore dell'espressione (*c) .
6 Fine della dichiarazione

Si noti che nessuno degli identificatori di cui sopra era visibile prima di questa dichiarazione e quindi le espressioni utilizzate non sarebbero valide prima di essa.

Dopo ciascuna di tali espressioni, l'identificatore utilizzato in esso viene introdotto nell'ambito corrente. (Se l'identificatore ha assegnato il collegamento a esso, può anche essere dichiarato nuovamente con lo stesso tipo di collegamento in modo che entrambi gli identificatori si riferiscano allo stesso oggetto o funzione)

Inoltre, per l'inizializzazione può essere utilizzato il segno di operatore uguale ( = ). Se un'espressione non valutata (dichiarante) è seguita da = all'interno della dichiarazione, diciamo che anche l'identificatore che viene introdotto viene inizializzato. Dopo il segno = possiamo mettere nuovamente un'espressione, ma questa volta verrà valutata e il suo valore sarà usato come iniziale per l'oggetto dichiarato.

Esempi:

int l = 90; /* the same as: */

int l; l = 90; /* if it the declaration of l was in block scope */

int c = 2, b[c]; /* ok, equivalent to: */

int c = 2; int b[c];

Più tardi nel tuo codice, puoi scrivere esattamente la stessa espressione dalla parte di dichiarazione dell'identificatore appena introdotto, dandoti un oggetto del tipo specificato all'inizio della dichiarazione, assumendo che tu abbia assegnato valori validi a tutti gli accessi oggetti nel modo. Esempi:

void f()
{
    int b2; /* you should be able to write later in your code b2 
            which will directly refer to the integer object
            that b2 identifies */
    
    b2 = 2; /* assign a value to b2 */
    
    printf("%d", b2); /*ok - should print 2*/

    int *b3; /* you should be able to write later in your code *b3 */

    b3 = &b2; /* assign valid pointer value to b3 */

    printf("%d", *b3); /* ok - should print 2 */

    int **b4; /* you should be able to write later in your code **b4 */

    b4 = &b3;

    printf("%d", **b4); /* ok - should print 2 */

    void (*p)(); /* you should be able to write later in your code (*p)() */

    p = &f; /* assign a valid pointer value */

    (*p)(); /* ok - calls function f by retrieving the
            pointer value inside p -    p
            and dereferencing it -      *p
            resulting in a function
            which is then called -      (*p)() -

            it is not *p() because else first the () operator is 
            applied to p and then the resulting void object is
            dereferenced which is not what we want here */
}

La dichiarazione di b3 specifica che è possibile utilizzare potenzialmente il valore b3 come media per accedere a qualche oggetto intero.

Ovviamente, per applicare l'indirezione ( * ) a b3 , dovresti anche avere un valore appropriato memorizzato in esso (vedi puntatori per maggiori informazioni). Si dovrebbe anche prima memorizzare qualche valore in un oggetto prima di provare a recuperarlo (si può vedere di più su questo problema qui ). Abbiamo fatto tutto questo negli esempi sopra.

int a3(); /* you should be able to call a3 */

Questo dice al compilatore che a3 chiamare a3 . In questo caso, a3 riferisce alla funzione anziché a un oggetto. Una differenza tra oggetto e funzione è che le funzioni avranno sempre una sorta di collegamento. Esempi:

void f1()
{
    {
        int f2(); /* 1 refers to some function f2 */
    }
    
    {
        int f2(); /* refers to the exact same function f2 as (1) */
    }
}

Nell'esempio sopra, le 2 dichiarazioni si riferiscono alla stessa funzione f2 , mentre se dichiarano oggetti, in questo contesto (avendo 2 diversi ambiti di blocco), sarebbero 2 oggetti distinti diversi.

int (*a3)(); /* you should be able to apply indirection to `a3` and then call it */

Ora sembra che si stia complicando, ma se conosci la precedenza degli operatori, avrai 0 problemi a leggere la dichiarazione di cui sopra. Le parentesi sono necessarie perché l'operatore * ha meno precedenza di quello ( ) .

Nel caso dell'uso dell'operatore di pedice, l'espressione risultante non sarebbe effettivamente valida dopo la dichiarazione perché l'indice utilizzato in esso (il valore all'interno [ e ] ) sarà sempre 1 sopra il valore massimo consentito per questo oggetto / funzione.

int a4[5]; /* here a4 shouldn't be accessed using the index 5 later on */

Ma dovrebbe essere accessibile da tutti gli altri indici inferiori a 5. Esempi:

a4[0], a4[1]; a4[4];

a4[5] si tradurrà in UB. Ulteriori informazioni sugli array sono disponibili qui .

int (*a5)[5](); /* here a4 could be applied indirection
                indexed up to (but not including) 5
                and called */

Sfortunatamente per noi, sebbene sintatticamente possibile, la dichiarazione di a5 è proibita dallo standard attuale.

typedef

I typedef sono dichiarazioni che hanno la parola chiave typedef davanti e prima del tipo. Per esempio:

typedef int (*(*t0)())[5];

( puoi inserire tecnicamente anche il typedef dopo il tipo - come questo int typedef (*(*t0)())[5]; ma questo è scoraggiato )

Le dichiarazioni di cui sopra dichiarano un identificatore per un nome typedef. Puoi usarlo in questo modo in seguito:

t0 pf;

Quale avrà lo stesso effetto della scrittura:

int (*(*pf)())[5];

Come puoi vedere il nome typedef "salva" la dichiarazione come un tipo da usare successivamente per altre dichiarazioni. In questo modo puoi salvare alcune sequenze di tasti. Anche come dichiarazione che usa typedef è ancora una dichiarazione che non sei limitato solo dall'esempio sopra:

t0 (*pf1);

Equivale a:

int (*(**pf1)())[5];

Utilizzare la regola destra o sinistra per decifrare la dichiarazione C

La regola "destra-sinistra" è una regola completamente regolare per decifrare le dichiarazioni C. Può anche essere utile per crearli.

Leggi i simboli mentre li incontri nella dichiarazione ...

*   as "pointer to"          - always on the left side
[]  as "array of"            - always on the right side
()  as "function returning"  - always on the right side

Come applicare la regola

PASSO 1

Trova l'identificatore. Questo è il tuo punto di partenza. Quindi di 'te stesso, "l'identificativo è". Hai iniziato la tua dichiarazione

PASSO 2

Guarda i simboli a destra dell'identificatore. Se, per esempio, trovi () lì, allora sai che questa è la dichiarazione per una funzione. Quindi avresti allora "identificatore è funzione di ritorno" . O se hai trovato un [] lì, diresti "identificatore è matrice di" . Continua a destra finché non finisci i simboli OPPURE premi una parentesi tonda ) . (Se si preme una parentesi tonda ( , questo è l'inizio di un simbolo () , anche se c'è qualcosa tra le parentesi.

PASSAGGIO 3

Guarda i simboli a sinistra dell'identificatore. Se non è uno dei nostri simboli sopra (diciamo qualcosa come "int"), dillo e basta. Altrimenti, traducilo in inglese usando la tabella sopra. Continuate a sinistra finché non finite i simboli O colpite una parentesi sulla sinistra ( .

Ora ripeti i passaggi 2 e 3 finché non hai formato la tua dichiarazione.


Ecco alcuni esempi:

int *p[];

Innanzitutto, trova l'identificatore:

int *p[];
     ^

"p è"

Ora, spostati verso destra finché non escono simboli o la parentesi tonda colpisce.

int *p[];
      ^^

"p è matrice di"

Non riesci più a muoverti (fuori dai simboli), quindi vai a sinistra e trova:

int *p[];
    ^

"p è una matrice di puntatore a"

Continua a sinistra e trova:

int *p[];
^^^

"p è una matrice di puntatore a int".

(o "p è un array in cui ogni elemento è di tipo puntatore a int" )

Un altro esempio:

int *(*func())();

Trova l'identificatore.

int *(*func())();
       ^^^^

"func is"

Vai a destra.

int *(*func())();
           ^^

"func is function return"

Non posso più muoverti a destra a causa della parentesi giusta, quindi vai a sinistra.

int *(*func())();
      ^

"func è la funzione che restituisce il puntatore a"

Non riesci più a spostarti a sinistra a causa della parentesi sinistra, quindi continua a andare a destra.

int *(*func())();
              ^^

"func è la funzione che restituisce il puntatore alla funzione che restituisce"

Non posso più muoverti perché non abbiamo più simboli, quindi vai a sinistra.

int *(*func())();
    ^

"func è la funzione che restituisce il puntatore alla funzione restituendo il puntatore a"

E infine, continua a sinistra, perché non è rimasto nulla a destra.

int *(*func())();
^^^

"func è la funzione che restituisce il puntatore alla funzione restituisce il puntatore a int".

Come puoi vedere, questa regola può essere abbastanza utile. Puoi anche usarlo per controllare se stessi mentre stai creando dichiarazioni e per darti un suggerimento su dove mettere il prossimo simbolo e se sono necessarie le parentesi.

Alcune dichiarazioni sembrano molto più complicate di quanto non siano dovute alle dimensioni degli array e agli elenchi di argomenti in forma di prototipo. Se vedi [3] , viene letto come "array (size 3) of ..." . Se vedi (char *,int) che viene letto come * "function expect (char , int) e return ..." .

Ecco uno divertente:

int (*(*fun_one)(char *,double))[9][20];

Non passerò attraverso ciascuno dei passaggi per decifrare questo.

* "fun_one è puntatore alla funzione che si aspetta (char , double) e restituisce il puntatore all'array (size 9) dell'array (size 20) di int."

Come puoi vedere, non è così complicato se ti sbarazzi delle dimensioni dell'array e degli elenchi di argomenti:

int (*(*fun_one)())[][];

È possibile decifrarlo in questo modo e quindi inserire le dimensioni dell'array e gli elenchi di argomenti in un secondo momento.

Alcune parole finali:


È abbastanza possibile fare dichiarazioni illegali usando questa regola, quindi è necessaria una certa conoscenza di ciò che è legale in C. Ad esempio, se quanto sopra fosse stato:

int *((*fun_one)())[][];

avrebbe letto "fun_one è un puntatore alla funzione che restituisce una matrice di puntatore a int" . Poiché una funzione non può restituire un array, ma solo un puntatore a un array, tale dichiarazione è illegale.

Le combinazioni illegali includono:

[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array

In tutti i casi di cui sopra, è necessario un insieme di parentesi per associare un simbolo * a sinistra tra questi simboli () e [] destra, affinché la dichiarazione sia legale.

Ecco alcuni esempi:


legale

int i;               an int
int *p;              an int pointer (ptr to an int)
int a[];             an array of ints
int f();             a function returning an int
int **pp;            a pointer to an int pointer (ptr to a ptr to an int)
int (*pa)[];         a pointer to an array of ints
int (*pf)();         a pointer to a function returning an int
int *ap[];           an array of int pointers (array of ptrs to ints)
int aa[][];          an array of arrays of ints
int *fp();           a function returning an int pointer
int ***ppp;          a pointer to a pointer to an int pointer
int (**ppa)[];       a pointer to a pointer to an array of ints
int (**ppf)();       a pointer to a pointer to a function returning an int
int *(*pap)[];       a pointer to an array of int pointers
int (*paa)[][];      a pointer to an array of arrays of ints
int *(*pfp)();       a pointer to a function returning an int pointer
int **app[];         an array of pointers to int pointers
int (*apa[])[];      an array of pointers to arrays of ints
int (*apf[])();      an array of pointers to functions returning an int
int *aap[][];        an array of arrays of int pointers
int aaa[][][];       an array of arrays of arrays of int
int **fpp();         a function returning a pointer to an int pointer
int (*fpa())[];      a function returning a pointer to an array of ints
int (*fpf())();      a function returning a pointer to a function returning an int

Illegale

int af[]();          an array of functions returning an int
int fa()[];          a function returning an array of ints
int ff()();          a function returning a function returning an int
int (*pfa)()[];      a pointer to a function returning an array of ints
int aaf[][]();       an array of arrays of functions returning an int
int (*paf)[]();      a pointer to a an array of functions returning an int
int (*pff)()();      a pointer to a function returning a function returning an int
int *afp[]();        an array of functions returning int pointers
int afa[]()[];       an array of functions returning an array of ints
int aff[]()();       an array of functions returning functions returning an int
int *fap()[];        a function returning an array of int pointers
int faa()[][];       a function returning an array of arrays of ints
int faf()[]();       a function returning an array of functions returning an int
int *ffp()();        a function returning a function returning an int pointer

Fonte: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow