Szukaj…


Uwagi

Deklaracja identyfikatora odnosząca się do obiektu lub funkcji jest często nazywana po prostu deklaracją obiektu lub funkcji.

Wywołanie funkcji z innego pliku 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;
}

Kompiluj i łącz

Najpierw kompilujemy zarówno pliki foo.c jak i main.c do plików obiektów . Tutaj używamy kompilatora gcc , twój kompilator może mieć inną nazwę i wymagać innych opcji.

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

Teraz łączymy je ze sobą, aby uzyskać nasz ostateczny plik wykonywalny:

$ gcc -o testprogram foo.o main.o

Korzystanie ze zmiennej globalnej

Stosowanie zmiennych globalnych jest ogólnie odradzane. Utrudnia to zrozumienie programu i utrudnia debugowanie. Ale czasami użycie zmiennej globalnej jest dopuszczalne.

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;
}

Zobacz także Jak używać extern do udostępniania zmiennych między plikami źródłowymi?

Korzystanie ze stałych globalnych

Nagłówków można używać do deklarowania globalnie używanych zasobów tylko do odczytu, takich jak na przykład tabele ciągów.

Zadeklaruj je w osobnym nagłówku, który zostanie dołączony do dowolnego pliku („ Translation Unit ”), który chce z nich skorzystać. Przydatne jest użycie tego samego nagłówka do zadeklarowania powiązanego wyliczenia w celu zidentyfikowania wszystkich zasobów łańcuchowych:

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

Aby faktycznie zdefiniować zasoby, utworzono powiązany plik .c, czyli inną jednostkę tłumaczeniową przechowującą rzeczywiste instancje tego, co zadeklarowano w powiązanym pliku nagłówkowym (.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"
};

Program używający tego może wyglądać następująco:

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;
}

Skompiluj trzy powyższe pliki za pomocą GCC i połącz je, aby stać się main plikiem programu, na przykład za pomocą tego:

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

(użyj tych -Wall -Wextra -pedantic -Wconversion aby kompilator był naprawdę wybredny, dzięki czemu niczego nie przegapisz przed opublikowaniem kodu w SO, powie świat, a nawet warto go wdrożyć do produkcji)

Uruchom utworzony program:

$ ./main

I dostać:

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

Wprowadzenie

Przykładami deklaracji są:

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

Powyższa deklaracja deklaruje pojedynczy identyfikator o nazwie a który odnosi się do jakiegoś obiektu o typie int .

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

Druga deklaracja deklaruje 2 identyfikatory o nazwach a1 i b1 które odnoszą się do niektórych innych obiektów, choć z tym samym typem int .

Zasadniczo sposób, w jaki to działa, jest taki - najpierw wpiszesz jakiś typ , a następnie napiszesz jedno lub wiele wyrażeń oddzielonych przecinkiem ( , ) ( które nie będą w tym momencie oceniane - i które w przeciwnym razie powinny być określane jako deklaratory w ten kontekst ). Pisząc takie wyrażenia, możesz zastosować tylko pośrednie ( * ), wywołanie funkcji ( ( ) ) lub indeks dolny (lub indeksowanie tablicy - [ ] ) do niektórych identyfikatorów (nie możesz też w ogóle używać żadnych operatorów). Używany identyfikator nie musi być widoczny w bieżącym zakresie. Kilka przykładów:

/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# Opis
1 Nazwa typu liczby całkowitej.
2) Nieocenione wyrażenie stosujące pośrednie do jakiegoś identyfikatora z .
3) Mamy przecinek wskazujący, że w tej samej deklaracji pojawi się jeszcze jedno wyrażenie.
4 Nieocenione wyrażenie stosujące pośrednie do innego identyfikatora x .
5 Nieocenione wyrażenie stosujące pośrednio do wartości wyrażenia (*c) .
6 Koniec deklaracji.

Zauważ, że żaden z powyższych identyfikatorów nie był widoczny przed tą deklaracją, więc użyte wyrażenia nie byłyby ważne przed nią.

Po każdym takim wyrażeniu zastosowany w nim identyfikator jest wprowadzany do bieżącego zakresu. (Jeśli identyfikator przypisał mu powiązanie, można go również zadeklarować ponownie tym samym rodzajem powiązania, aby oba identyfikatory odnosiły się do tego samego obiektu lub funkcji)

Dodatkowo do inicjalizacji można użyć znaku równości operatora ( = ). Jeśli po nieuwartościowanym wyrażeniu (deklarator) następuje znak = w deklaracji - mówimy, że wprowadzany identyfikator jest również inicjalizowany. Po znaku = możemy jeszcze raz wstawić wyrażenie, ale tym razem zostanie ono ocenione i jego wartość zostanie użyta jako początkowa dla zadeklarowanego obiektu.

Przykłady:

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];

Później w kodzie możesz zapisać dokładnie to samo wyrażenie z części deklaracji nowo wprowadzonego identyfikatora, dając ci obiekt typu określonego na początku deklaracji, zakładając, że przypisałeś prawidłowe wartości wszystkim dostępnym obiekty na drodze. Przykłady:

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 */
}

Deklaracja b3 określa, że potencjalnie możesz użyć wartości b3 jako środka dostępu do jakiegoś obiektu całkowitego.

Oczywiście, aby zastosować pośrednictwo ( * ) do b3 , powinieneś również mieć w nim odpowiednią wartość (zobacz wskaźniki, aby uzyskać więcej informacji). Powinieneś również najpierw zapisać pewną wartość w obiekcie, zanim spróbujesz go odzyskać (więcej informacji na temat tego problemu znajdziesz tutaj ). Zrobiliśmy to wszystko w powyższych przykładach.

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

Ten informuje kompilator, że spróbujesz wywołać a3 . W tym przypadku a3 odnosi się do funkcji zamiast obiektu. Jedną różnicą między przedmiotem a funkcją jest to, że funkcje zawsze będą miały pewien rodzaj powiązania. Przykłady:

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

W powyższym przykładzie 2 deklaracje odnoszą się do tej samej funkcji f2 , podczas gdy gdyby deklarowały obiekty, to w tym kontekście (posiadające 2 różne zakresy bloków) byłyby to 2 różne odrębne obiekty.

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

Teraz może się to wydawać skomplikowane, ale jeśli znasz pierwszeństwo operatorów, będziesz mieć 0 problemów z odczytaniem powyższej deklaracji. Nawiasy są potrzebne, ponieważ operator * ma mniejszy priorytet niż operator ( ) .

W przypadku użycia operatora indeksu dolnego wynikowe wyrażenie nie byłoby w rzeczywistości poprawne po deklaracji, ponieważ zastosowany w nim indeks (wartość wewnątrz [ i ] ) zawsze będzie wynosił 1 powyżej maksymalnej dozwolonej wartości dla tego obiektu / funkcji.

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

Ale powinien być dostępny dla wszystkich innych indeksów niższych niż 5. Przykłady:

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

a4[5] spowoduje UB. Więcej informacji o tablicach można znaleźć tutaj .

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

Na nieszczęście dla nas, chociaż możliwe pod względem składniowym, deklaracja a5 jest zabroniona przez obecny standard.

Typedef

Typedefs to deklaracje zawierające słowo kluczowe typedef z przodu i przed typem. Na przykład:

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

( technicznie możesz wstawić typedef po typie - tak jak ten int typedef (*(*t0)())[5]; ale to jest odradzane )

Powyższe deklaracje deklarują identyfikator nazwy typedef. Możesz użyć go w następujący sposób:

t0 pf;

Który będzie miał taki sam efekt jak pisanie:

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

Jak widać nazwa typedef „zapisuje” deklarację jako typ do późniejszego wykorzystania w innych deklaracjach. W ten sposób możesz zapisać niektóre naciśnięcia klawiszy. Ponieważ deklaracja z użyciem typedef jest nadal deklaracją, nie ogranicza Cię tylko powyższy przykład:

t0 (*pf1);

Jest taki sam jak:

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

Użycie reguły prawo-lewo lub spirali do odszyfrowania deklaracji C.

Reguła „prawa-lewa” jest całkowicie regularną zasadą odszyfrowywania deklaracji C. Może być również przydatny w ich tworzeniu.

Przeczytaj symbole, jakie napotkasz w deklaracji ...

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

Jak zastosować regułę

KROK 1

Znajdź identyfikator. To jest twój punkt wyjścia. Następnie powiedz sobie: „identyfikator jest”. Rozpocząłeś swoją deklarację.

KROK 2

Spójrz na symbole po prawej stronie identyfikatora. Jeśli, powiedzmy, znajdziesz () tam, to wiesz, że jest to deklaracja funkcji. Więc miałbyś wtedy „identyfikator zwraca funkcję” . Lub jeśli znalazłeś tam [] , powiedziałbyś „identyfikator to tablica” . Kontynuuj w prawo, aż zabraknie symboli LUB naciśniesz prawy nawias ) . (Jeśli naciśniesz lewy nawias ( to początek symbolu () , nawet jeśli między nawiasami jest coś. Więcej na ten temat poniżej).

KROK 3

Spójrz na symbole po lewej stronie identyfikatora. Jeśli nie jest to jeden z naszych powyższych symboli (powiedzmy coś w stylu „int”), po prostu powiedz to. W przeciwnym razie przetłumacz go na angielski, korzystając z powyższej tabeli. Idź cały czas w lewo, aż zabraknie symboli LUB uderzysz w lewy nawias ( .

Teraz powtarzaj kroki 2 i 3, aż utworzysz deklarację.


Oto kilka przykładów:

int *p[];

Najpierw znajdź identyfikator:

int *p[];
     ^

„p jest”

Teraz idź w prawo, aż znikniesz z symboli lub naciśniesz prawy nawias.

int *p[];
      ^^

„p jest tablicą”

Nie można już poruszać się w prawo (poza symbolami), więc przesuń się w lewo i znajdź:

int *p[];
    ^

„p to tablica wskaźnika do”

Idź w lewo i znajdź:

int *p[];
^^^

„p jest tablicą wskaźnika na int”.

(lub „p jest tablicą, w której każdy element ma wskaźnik typu na int” )

Inny przykład:

int *(*func())();

Znajdź identyfikator.

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

„func is”

Ruch w prawo.

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

„func zwraca funkcję”

Nie można już poruszać się w prawo z powodu prawego nawiasu, więc przesuń się w lewo.

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

„func to funkcja zwracająca wskaźnik do”

Nie można już poruszać się w lewo z powodu lewego nawiasu, więc idź dalej w prawo.

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

„func to wskaźnik zwracający funkcję do zwracającej funkcję”

Nie można już poruszać się w prawo, ponieważ brakuje nam symboli, więc idź w lewo.

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

„func to funkcja zwracająca wskaźnik do funkcji zwracająca wskaźnik do”

I w końcu idź dalej w lewo, ponieważ po prawej nic nie pozostało.

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

msgstr "func to funkcja zwracająca wskaźnik do funkcji zwracająca wskaźnik do int".

Jak widać, ta zasada może być bardzo przydatna. Możesz go również użyć do sprawdzenia zdrowia psychicznego podczas tworzenia deklaracji oraz do podpowiedzi, gdzie umieścić następny symbol i czy potrzebne są nawiasy.

Niektóre deklaracje wyglądają na znacznie bardziej skomplikowane niż ze względu na rozmiary tablic i listy argumentów w formie prototypowej. Jeśli widzisz [3] , jest to czytane jako „tablica (rozmiar 3) z…” . Jeśli zobaczysz (char *,int) , będzie to odczytane jako * „funkcja oczekująca (char , int) i zwracająca ...” .

Oto zabawny:

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

Nie będę przechodził przez każdy krok, aby go rozszyfrować.

* „fun_one jest wskaźnikiem funkcji oczekującym (char , double) i zwracającym wskaźnik do tablicy (rozmiar 9) tablicy (rozmiar 20) int.”

Jak widać, nie jest to tak skomplikowane, jeśli pozbędziesz się rozmiarów tablic i list argumentów:

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

Możesz odszyfrować go w ten sposób, a następnie wstawić rozmiary tablic i listy argumentów później.

Kilka ostatnich słów:


Możliwe jest składanie nielegalnych deklaracji przy użyciu tej reguły, więc niezbędna jest pewna wiedza na temat legalności w języku C. Na przykład, jeśli powyższe było:

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

napisałby „fun_one jest wskaźnikiem funkcji zwracającym tablicę tablicy wskaźnika do wartości int” . Ponieważ funkcja nie może zwrócić tablicy, a jedynie wskaźnik do tablicy, deklaracja jest nielegalna.

Nielegalne kombinacje obejmują:

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

We wszystkich powyższych przypadkach potrzebujesz zestawu nawiasów, aby powiązać symbol * po lewej stronie między tymi symbolami () i [] prawej stronie, aby deklaracja była legalna.

Oto kilka innych przykładów:


Prawny

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

Nielegalny

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

Źródło: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow