Поиск…


замечания

Объявление идентификатора, ссылающегося на объект или функцию, часто упоминается как краткое как просто объявление объекта или функции.

Вызов функции из другого файла 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;
}

Компиляция и ссылка

Сначала мы скомпилируем файлы foo.c и main.c в объектные файлы . Здесь мы используем компилятор gcc , ваш компилятор может иметь другое имя и нуждаться в других параметрах.

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

Теперь мы связываем их вместе для создания нашего окончательного исполняемого файла:

$ gcc -o testprogram foo.o main.o

Использование глобальной переменной

Использование глобальных переменных обычно обескураживается. Это делает вашу программу более трудной для понимания и сложнее отлаживать. Но иногда использование глобальной переменной приемлемо.

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

См. Также Как использовать extern для обмена переменными между исходными файлами?

Использование глобальных констант

Заголовки могут использоваться, чтобы объявлять глобально используемые ресурсы только для чтения, например таблицы строк.

Объявите те в отдельном заголовке, который включается в любой файл (« Блок переводов »), который хочет их использовать. Удобно использовать один и тот же заголовок, чтобы объявить связанное перечисление для идентификации всех строковых ресурсов:

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

Чтобы фактически определить ресурсы, созданные связанным .c-файлом, это еще одна единица перевода, содержащая фактические экземпляры того, что было объявлено в соответствующем файле заголовка (.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"
};

Программа, использующая это, может выглядеть так:

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

Скомпилируйте три файла выше, используя GCC, и соедините их, чтобы стать main файлом программы, например, используя это:

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

(используйте эту -Wall -Wextra -pedantic -Wconversion чтобы сделать компилятор действительно придирчивым, поэтому вы не пропустите ничего, прежде чем отправлять код в SO, скажете мир или даже поместите его в производство)

Запустить созданную программу:

$ ./main

И получить:

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

Вступление

Пример объявлений:

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

В приведенном выше объявлении объявляется один идентификатор с именем a который относится к некоторому объекту с типом int .

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

Второе объявление объявляет 2 идентификатора с именами a1 и b1 которые относятся к некоторым другим объектам, но с тем же типом int .

В принципе, способ, которым это работает, выглядит следующим образом: сначала вы помещаете какой-то тип , затем вы пишете одно или несколько выражений, разделенных через запятую ( , ) ( которые не будут оцениваться в этой точке), и которые в противном случае следует называть деклараторами в этот контекст ). При написании таких выражений вам разрешено применять только некоторые индексы ( * ), функции call ( ( ) ) или индексы (или индексирования массива - [ ] ) к некоторому идентификатору (вы также не можете использовать каких-либо операторов). Используемый идентификатор не требуется быть видимым в текущей области. Некоторые примеры:

/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# Описание
1 Имя целочисленного типа.
2 Невыраженное выражение, применяющее косвенность к некоторому идентификатору z .
3 У нас есть запятая, указывающая, что еще одно выражение будет следовать в той же декларации.
4 Невыраженное выражение, применяющее косвенность к другому идентификатору x .
5 Невыраженное выражение, применяющее косвенность к значению выражения (*c) .
6 Конец объявления.

Обратите внимание: ни один из вышеперечисленных идентификаторов не был видимым до этого объявления, и поэтому используемые выражения не будут действительны перед ним.

После каждого такого выражения используемый в нем идентификатор вводится в текущую область. (Если идентификатор назначил ему привязку, он также может быть повторно объявлен с помощью того же типа связи, чтобы оба идентификатора ссылались на один и тот же объект или функцию)

Кроме того, для инициализации может использоваться знак равенства ( = ). Если за объявлением не оценено выражение (декларатор), то = внутри декларации - мы говорим, что вводимый идентификатор также инициализируется. После знака = мы можем снова добавить некоторое выражение, но на этот раз оно будет оценено, и его значение будет использоваться как начальное для объявленного объекта.

Примеры:

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

Позже в вашем коде вам разрешено записывать то же самое выражение из части объявления нового введенного идентификатора, предоставляя вам объект типа, указанного в начале объявления, при условии, что вы присвоили действительные значения всем доступным объектов в пути. Примеры:

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

Объявление b3 указывает, что вы можете использовать значение b3 как среднее для доступа к некоторому целочисленному объекту.

Конечно, для применения косвенности ( * ) к b3 , вы также должны иметь правильное значение, хранящееся в нем (подробнее см. Указатели ). Вы также должны сначала сохранить некоторое значение в объект, прежде чем пытаться его извлечь (вы можете увидеть больше об этой проблеме здесь ). Мы все это сделали в приведенных выше примерах.

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

Это говорит компилятору, что вы попытаетесь вызвать a3 . В этом случае a3 ссылается на функцию вместо объекта. Одна разница между объектом и функцией заключается в том, что функции всегда будут иметь какую-то связь. Примеры:

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

В приведенном выше примере 2 объявления относятся к одной и той же функции f2 , тогда как если бы они объявляли объекты, то в этом контексте (с двумя различными блочными областями) у них было бы два разных разных объекта.

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

Теперь это может показаться сложным, но если вы знаете приоритет операторов, у вас будет 0 проблем с чтением вышеуказанной декларации. Скобки нужны, потому что оператор * имеет меньшее приоритет, чем ( ) .

В случае использования оператора подстроки результирующее выражение не будет действительно действительным после объявления, потому что индекс, используемый в нем (значение внутри [ и ] ), всегда будет на 1 превышать максимально допустимое значение для этого объекта / функции.

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

Но он должен быть доступен всеми другими индексами ниже 5. Примеры:

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

a4[5] приведет к UB. Более подробную информацию о массивах можно найти здесь .

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

К сожалению для нас, хотя и синтаксически возможно, декларация a5 запрещена действующим стандартом.

Typedef

Typedefs - это объявления с ключевым словом typedef впереди и перед типом. Например:

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

( вы можете технически поставить typedef после типа тоже - как это int typedef (*(*t0)())[5]; но это не рекомендуется )

Вышеуказанные объявления объявляют идентификатор для имени typedef. Вы можете использовать его следующим образом:

t0 pf;

Что будет иметь такой же эффект, как и запись:

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

Как вы можете видеть, имя typedef «сохраняет» объявление как тип, который будет использоваться позже для других объявлений. Таким образом вы можете сэкономить несколько нажатий клавиш. Также как объявление с использованием typedef все еще является объявлением, вы не ограничены только приведенным выше примером:

t0 (*pf1);

Такой же как:

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

Использование правого или левого правила для расшифровки декларации C

Правило «право-левое» является полностью регулярным правилом для расшифровки объявлений C. Он также может быть полезен при их создании.

Прочтите символы, когда вы встретите их в объявлении ...

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

Как применить правило

ШАГ 1

Найдите идентификатор. Это ваша отправная точка. Тогда скажите себе: «Идентификатор есть». Вы начали свою декларацию.

ШАГ 2

Посмотрите на символы справа от идентификатора. Если, скажем, вы найдете () там, то вы знаете, что это объявление для функции. Таким образом, у вас тогда будет «идентификатор возвращается функция» . Или, если вы нашли там [] , вы бы сказали: «Идентификатор - это массив» . Продолжайте, пока не закончите символы ИЛИ не попадете в правую скобку ) . (Если вы нажмете левую круглую скобку ( это начало символа () , даже если между скобками есть вещи. Подробнее об этом ниже.)

ШАГ 3

Посмотрите на символы слева от идентификатора. Если это не один из наших символов выше (скажем, что-то вроде «int»), просто скажите это. В противном случае переведите его на английский, используя приведенную выше таблицу. Продолжайте движение до тех пор, пока не закончите символы ИЛИ не удалите левую скобку ( .

Теперь повторите шаги 2 и 3, пока вы не сформируете свою декларацию.


Вот некоторые примеры:

int *p[];

Во-первых, найдите идентификатор:

int *p[];
     ^

"p"

Теперь двигайтесь прямо, пока из символов или правой скобки не попадет.

int *p[];
      ^^

"p является массивом"

Больше не может двигаться (из символов), поэтому переместите влево и найдите:

int *p[];
    ^

"p - массив указателя на"

Продолжайте движение влево и найдите:

int *p[];
^^^

«p - массив указателя на int».

(или «p - массив, в котором каждый элемент имеет указатель типа на int» )

Другой пример:

int *(*func())();

Найдите идентификатор.

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

"func is"

Двигаться вправо.

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

«func возвращает функцию»

Больше не может двигаться дальше из-за правильной круглой скобки, поэтому двигайтесь влево.

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

"func - это функция, возвращающая указатель на"

Больше не может двигаться влево из-за левой скобки, так что продолжайте идти вправо.

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

«func - это функция, возвращающая указатель на функцию возврата»

Не могу больше двигаться, потому что у нас нет символов, поэтому идите налево.

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

"func - это функция, возвращающая указатель на функцию, возвращающую указатель на"

И, наконец, продолжайте идти, потому что справа ничего не осталось.

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

«func - это функция, возвращающая указатель на функцию, возвращающую указатель на int».

Как вы можете видеть, это правило может быть весьма полезным. Вы также можете использовать его для проверки здравомыслия во время создания объявлений и дать вам подсказку о том, где поставить следующий символ, и нужны ли скобки.

Некоторые декларации выглядят намного сложнее, чем из-за размеров массивов и списков аргументов в прототипе. Если вы видите [3] , это читается как «массив (размер 3) ...» . Если вы видите (char *,int) который читается как * "function expecting (char , int) и возврат ..." .

Вот весело:

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

Я не буду проходить каждый шаг, чтобы расшифровать этот.

* «fun_one - это указатель на функцию expecting (char , double) и возвращающий указатель на массив (размер 9) массива (размер 20) для int».

Как вы можете видеть, это не так сложно, если вы избавитесь от размеров массивов и списков аргументов:

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

Вы можете расшифровать его таким образом, а затем поместить в массивы размеры и списки аргументов позже.

Некоторые заключительные слова:


Вполне возможно сделать незаконные объявления, используя это правило, поэтому необходимо знать некоторые из них, что является законным в C. Например, если выше было:

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

он бы прочитал «fun_one - это указатель на функцию, возвращающую массив массива указателя на int» . Поскольку функция не может вернуть массив, но только указатель на массив, это объявление является незаконным.

Незаконные комбинации включают:

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

Во всех вышеперечисленных случаях вам понадобится набор круглых скобок для привязки символа * слева от этих () и [] правых символов, чтобы объявление было законным.

Вот еще несколько примеров:


легальный

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

нелегальный

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

Источник: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow