Синтаксис C - C syntax

Синтаксис языка программирования Си является набор правил , регулирующих написание программного обеспечения на языке C . Он разработан для использования в программах, которые очень краткие, имеют тесную связь с результирующим объектным кодом и, тем не менее, обеспечивают относительно высокоуровневую абстракцию данных . C был первым широко успешным языком высокого уровня для разработки переносимых операционных систем .

Синтаксис C использует принцип максимального жевания .

Структуры данных

Примитивные типы данных

В языке C числа представлены в трех формах: целочисленной , действительной и комплексной . Это различие отражает аналогичные различия в архитектуре набора команд большинства центральных процессоров . Целые типы данных хранят числа в наборе целых чисел , а действительные и комплексные числа представляют числа (или пары чисел) в наборе действительных чисел в форме с плавающей запятой .

Все целочисленные типы C имеют signedи unsignedварианты. Если signedили unsignedне указано явно, в большинстве случаев signedпредполагается. Однако по историческим причинам равнина char- это тип, отличный от обоих signed charи unsigned char. Это может быть тип со знаком или без знака, в зависимости от компилятора и набора символов (C гарантирует, что члены базового набора символов C имеют положительные значения). Кроме того, типы битовых полей, заданные как простые, intмогут быть знаковыми или беззнаковыми, в зависимости от компилятора.

Целочисленные типы

Целочисленные типы C бывают разных фиксированных размеров, способных представлять различные диапазоны чисел. Тип charзанимает ровно один байт (наименьший адресный блок памяти), который обычно имеет ширину 8 бит. (Хотя charможет представлять какой - либо из «основных» символов языка C, более широкий тип может потребоваться для международных наборов символов.) Большинство типов целочисленные оба подписанных и неподписанных сорта, назначаемые этим signedи unsignedключевым словам. Подписанные целочисленные типы могут использовать двоичное дополнение , обратный код , или знак-и магнитуды представление . Во многих случаях существует несколько эквивалентных способов обозначения типа; например, signed short intи shortявляются синонимами.

Представление некоторых типов может включать в себя неиспользуемые биты «заполнения», которые занимают память, но не включены в ширину. В следующей таблице представлен полный список стандартных целочисленных типов и их минимально допустимая ширина (включая любой знаковый бит).

Спецификации стандартных целочисленных типов
Кратчайшая форма спецификатора Минимальная ширина (бит)
_Bool 1
char 8
signed char 8
unsigned char 8
short 16
unsigned short 16
int 16
unsigned int 16
long 32
unsigned long 32
long long 64
unsigned long long 64

charТип отличается от обоих signed charи unsigned char, но гарантированно иметь такое же представление , как один из них. _BoolИ long longтипы унифицированы с 1999 года, и может не поддерживаться старыми компиляторами. Тип _Boolобычно доступен через typedefимя, boolопределенное стандартным заголовком stdbool.h .

В общем, ширина и схема представления, реализованная для любой данной платформы, выбирается на основе архитектуры машины, с некоторым учетом простоты импорта исходного кода, разработанного для других платформ. Ширина intтипа особенно широко варьируется среди реализаций C; он часто соответствует наиболее «естественному» размеру слова для конкретной платформы. Стандартный заголовок limits.h определяет макросы для минимальных и максимальных представимых значений стандартных целочисленных типов, реализованных на любой конкретной платформе.

В дополнение к стандартным целочисленным типам могут быть другие «расширенные» целочисленные типы, которые можно использовать для typedefs в стандартных заголовках. Для более точной спецификации ширины программисты могут и должны использовать typedefs из стандартного заголовка stdint.h .

Целочисленные константы могут быть указаны в исходном коде несколькими способами. Числовые значения могут быть указаны как десятичные (пример :)1022 , восьмеричные с нулем (0) в качестве префикса ( 01776) или шестнадцатеричные с 0x (ноль x) в качестве префикса ( 0x3FE). Символ в одинарных кавычках (пример :) 'R', называемый «символьной константой», представляет значение этого символа в наборе символов выполнения с типом int. За исключением символьных констант, тип целочисленной константы определяется шириной, необходимой для представления указанного значения, но всегда не меньше, чем int. Это можно изменить, добавив явный модификатор длины и / или подписи; например, 12luимеет тип unsigned long. Нет отрицательных целочисленных констант, но тот же эффект часто можно получить, используя унарный оператор отрицания «-».

Нумерованный тип

Перечислимого типа в С, указанный с enumключевым словом, и часто просто называют «перечисление» ( как правило , выраженный ee'-Num /ˌi.nʌm/ или ee'-noom /ˌi.nuːm/), представляет собой тип предназначен для представления значений через серию именованных констант. Каждая из перечисленных констант имеет тип int. Каждый enumтип сам по себе совместим с charцелочисленным типом со знаком или без знака, но каждая реализация определяет свои собственные правила для выбора типа.

Некоторые компиляторы предупреждают, если объекту с перечислимым типом присвоено значение, не являющееся одной из его констант. Однако такому объекту могут быть присвоены любые значения в диапазоне их совместимого типа, а enumконстанты можно использовать везде, где ожидается целое число. По этой причине enumзначения часто используются вместо #defineдиректив препроцессора для создания именованных констант. Такие константы, как правило, безопаснее использовать, чем макросы, поскольку они находятся в определенном пространстве имен идентификаторов.

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

enum colors { RED, GREEN, BLUE = 5, YELLOW } paint_color;

Это объявляет enum colorsтип; эти intконстанты RED(значение которого равно 0), GREEN(значение которого на единицу больше , чем RED, 1), BLUE(значение которого является данное значение, 5), и YELLOW(значение которого на единицу больше , чем BLUE, 6); и enum colorsпеременная paint_color. Константы могут использоваться вне контекста перечисления (где разрешено любое целочисленное значение), и значения, отличные от констант, могут быть присвоены paint_colorили любой другой переменной типа enum colors.

Типы с плавающей запятой

Форма с плавающей запятой используется для представления чисел с дробной составляющей. Однако они не представляют в точности наиболее рациональные числа; вместо этого они являются близким приближением. Существует три типа реальных значений, обозначаемых их спецификаторами: одинарная точность ( float), двойная точность ( double) и двойная расширенная точность ( long double). Каждый из них может представлять значения в другой форме, часто в одном из форматов с плавающей запятой IEEE .

Типы с плавающей запятой
Спецификаторы типа Точность (десятичные цифры) Диапазон экспоненты
Минимум IEEE 754 Минимум IEEE 754
float 6 7.2 (24 бит) ± 37 ± 38 (8 бит)
double 10 15,9 (53 бит) ± 37 ± 307 (11 бит)
long double 10 34,0 (113 бит) ± 37 ± 4931 (15 бит)

Константы с плавающей запятой могут быть записаны в десятичной системе счисления , например 1.23. Десятичное научное представление может использоваться путем добавления eили Eпоследующего десятичного показателя степени, также известного как обозначение E , например 1.23e2(которое имеет значение 1,23 × 10 2 = 123,0). Требуется либо десятичная точка, либо показатель степени (в противном случае число анализируется как целочисленная константа). Шестнадцатеричные константы с плавающей запятой подчиняются аналогичным правилам, за исключением того, что они должны иметь префикс 0xи использовать pили Pдля указания двоичной экспоненты, например 0xAp-2(который имеет значение 2,5, поскольку A h × 2 −2 = 10 × 2 −2 = 10 ÷ 4 ). Как десятичные, так и шестнадцатеричные константы с плавающей запятой могут быть дополнены суффиксом fили Fдля обозначения константы типа float, l(буквой l) или Lдля обозначения типа long double, или оставлены без суффикса для doubleконстанты.

Стандартный заголовочный файл float.hопределяет минимальные и максимальные значения типов с плавающей точкой по осуществлению, float, doubleи long double. Он также определяет другие ограничения, относящиеся к обработке чисел с плавающей запятой.

Спецификаторы класса хранения

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

Классы хранения
Спецификаторы Продолжительность жизни Объем Инициализатор по умолчанию
auto Блок (стек) Блокировать Неинициализированный
register Блок (стек или регистр ЦП) Блокировать Неинициализированный
static Программа Блок или единица компиляции Нуль
extern Программа Глобальный (вся программа) Нуль
(нет) 1 Динамический (куча) Неинициализировано (инициализировано, 0если используется calloc())
1 Выделено и освобождаться , используя malloc()и free()функции библиотеки.

Переменные, объявленные в блоке по умолчанию, имеют автоматическое хранение, как и переменные, явно объявленные с помощью спецификаторов autoили registerкласса хранения. Спецификаторы autoи registerмогут использоваться только в функциях и объявлениях аргументов функций; как таковой autoспецификатор всегда избыточен. Объекты, объявленные вне всех блоков и явно объявленные с помощью staticспецификатора класса хранения, имеют статическую продолжительность хранения. Статические переменные по умолчанию инициализируются компилятором равными нулю .

Объекты с автоматическим хранением являются локальными по отношению к блоку, в котором они были объявлены, и отбрасываются при выходе из блока. Кроме того, объектам, объявленным с registerклассом хранения, может быть предоставлен более высокий приоритет компилятором для доступа к регистрам ; хотя компилятор может решить не сохранять ни один из них в регистре. Объекты с этим классом хранения нельзя использовать с &унарным оператором address-of ( ). Объекты со статической памятью сохраняются на протяжении всей программы. Таким образом, функция может получить доступ к одному и тому же объекту через несколько вызовов. Объекты с выделенной продолжительностью хранения создаются и уничтожаются в явном виде с malloc, freeи связанными с ними функциями.

Спецификатор externкласса хранения указывает, что хранилище для объекта было определено в другом месте. При использовании внутри блока это указывает, что хранилище было определено объявлением вне этого блока. При использовании вне всех блоков это указывает на то, что хранилище было определено вне единицы компиляции. Спецификатор externкласса хранения является избыточным при использовании в объявлении функции. Это указывает на то, что объявленная функция была определена вне модуля компиляции.

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

Квалификаторы типа

Типы могут быть квалифицированы для указания особых свойств их данных. Квалификатор типа constуказывает, что значение не изменяется после инициализации. Попытка изменить constквалифицированное значение приводит к неопределенному поведению, поэтому некоторые компиляторы C сохраняют их в роданных или (для встроенных систем) в постоянной памяти (ROM). Квалификатор типа volatileуказывает оптимизирующему компилятору, что он не может удалять явно избыточные операции чтения или записи, поскольку значение может измениться, даже если оно не было изменено каким-либо выражением или оператором, или может потребоваться несколько операций записи, например, для отображаемого в память I / вывода .

Неполные типы

Неполный тип - это тип структуры или объединения , члены которого еще не указаны, тип массива , размерность которого еще не определена, или voidтип ( voidтип не может быть завершен). Такой тип нельзя создать (его размер неизвестен), а также нельзя получить доступ к его членам (они тоже неизвестны); однако можно использовать производный тип указателя (но не разыменовать).

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

struct thing *pt;

Объявляет ptкак указатель struct thing и неполного типа struct thing. Указатели на данные всегда имеют одинаковую ширину байта независимо от того, на что они указывают, поэтому этот оператор действителен сам по себе (до тех пор, ptпока не разыменован). Неполный тип можно дополнить позже в той же области, повторно объявив его:

struct thing {
    int num;
}; /* thing struct type is now completed */

Неполные типы используются для реализации рекурсивных структур; тело объявления типа может быть отложено до более позднего времени в единице перевода:

typedef struct Bert Bert;
typedef struct Wilma Wilma;

struct Bert {
    Wilma *wilma;
};

struct Wilma {
    Bert *bert;
};

Неполные типы также используются для сокрытия данных ; неполный тип определяется в файле заголовка , а тело - только в соответствующем исходном файле.

Указатели

В объявлениях модификатор звездочки ( *) указывает тип указателя. Например, если спецификатор intотносится к целочисленному типу, спецификатор int*относится к типу «указатель на целое число». Значения указателя связывают две части информации: адрес памяти и тип данных. В следующей строке кода объявляется переменная указателя на целое число с именем ptr :

int *ptr;

Ссылка

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

int a = 0;
int *ptr = &a;

Для этого используется оператор «адресации» (унарный &). Он создает ячейку памяти для следующего объекта данных.

Разыменование

К указанным данным можно получить доступ через значение указателя. В следующем примере целочисленной переменной b присвоено значение целочисленной переменной a , равное 10:

int a=10;
int *p;
p = &a;
int b = *p;

Для выполнения этой задачи используется унарный оператор разыменования , обозначенный звездочкой (*). Он возвращает данные, на которые указывает его операнд, который должен иметь тип указателя. Таким образом, выражение * p обозначает то же значение, что и a . Разыменование нулевого указателя недопустимо.

Массивы

Определение массива

Массивы используются в C для представления структур последовательных элементов одного и того же типа. Определение массива (фиксированного размера) имеет следующий синтаксис:

int array[100];

который определяет массив с именем array для хранения 100 значений примитивного типа int. Если объявляется в функции, измерение массива также может быть непостоянным выражением, и в этом случае будет выделена память для указанного количества элементов. В большинстве случаев при дальнейшем использовании упоминание о массиве переменных преобразуется в указатель на первый элемент в массиве. sizeofОператор является исключением: sizeof arrayдает размер всего массива (то есть, в 100 раз больше легкой intи sizeof(array) / sizeof(int)вернет 100). Другим исключением является оператор & (адрес-из), который возвращает указатель на весь массив, например

int (*ptr_to_array)[100] = &array;

Доступ к элементам

Основным средством доступа к значениям элементов массива является оператор индекса массива. Чтобы получить доступ к i -индексированному элементу массива , синтаксис будет иметь вид array[i], который относится к значению, хранящемуся в этом элементе массива.

Нумерация индексов массива начинается с 0 (см. Индексирование с нуля ). Таким образом, самый большой допустимый индекс массива равен количеству элементов в массиве минус 1. Чтобы проиллюстрировать это, рассмотрим объявленный массив a как имеющий 10 элементов; первый элемент будет a[0]и последний элемент будет a[9].

C не предоставляет возможности автоматической проверки границ для использования массива. Хотя логически последний индекс в массиве из 10 элементов будет 9, индексы 10, 11 и т. Д. Могут быть случайно указаны с неопределенным результатом.

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

Индексы массива против арифметики указателя
Элемент Первый Второй В третьих п- е
Индекс массива array[0] array[1] array[2] array[n - 1]
Разыменованный указатель *array *(array + 1) *(array + 2) *(array + n - 1)

Поскольку выражение a[i]семантически эквивалентно *(a+i), что, в свою очередь, эквивалентно *(i+a), выражение также можно записать как i[a], хотя эта форма используется редко.

Массивы переменной длины

C99 стандартизованные массивы переменной длины (VLA) в пределах блока. Такие переменные массива выделяются на основе значения целочисленного значения во время выполнения при входе в блок и освобождаются в конце блока. Начиная с C11, компилятор больше не требует реализации этой функции.

int n = ...;
int a[n];
a[3] = 10;

Этот синтаксис создает массив, размер которого фиксирован до конца блока.

Динамические массивы

Массивы которые могут быть изменены динамически могут быть получены с помощью стандартной библиотеки C . mallocФункция обеспечивает простой способ выделения памяти. Требуется один параметр: объем выделяемой памяти в байтах. После успешного выделения mallocвозвращает voidзначение указателя generic ( ), указывающее на начало выделенного пространства. Возвращаемое значение указателя неявно преобразуется в соответствующий тип путем присваивания. Если выделение не может быть завершено, mallocвозвращает нулевой указатель . Следовательно, следующий сегмент аналогичен по функциям приведенному выше желаемому объявлению:

#include <stdlib.h> /* declares malloc */
...
int *a = malloc(n * sizeof *a);
a[3] = 10;

Результатом является «указатель на int» переменную ( а ), которая указывает на первый из n смежных intобъектов; из-за эквивалентности указателя массива его можно использовать вместо фактического имени массива, как показано в последней строке. Преимущество использования этого динамического распределения заключается в том, что объем выделяемой ему памяти может быть ограничен тем, что действительно необходимо во время выполнения, и это может быть изменено по мере необходимости (с помощью стандартной библиотечной функции realloc).

Когда динамически выделяемая память больше не нужна, ее следует вернуть системе времени выполнения. Это делается с помощью вызова freeфункции. Он принимает единственный параметр: указатель на ранее выделенную память. Это значение, которое было возвращено предыдущим вызовом malloc.

Затем в качестве меры безопасности некоторые программисты устанавливают для переменной-указателя значение NULL:

free(a);
a = NULL;

Это гарантирует, что дальнейшие попытки разыменования указателя на большинстве систем приведут к сбою программы. Если этого не сделать, переменная становится висящим указателем, что может привести к ошибке использования после освобождения. Однако, если указатель является локальной переменной, установка для NULLнее значения не запрещает программе использовать другие копии указателя. Статические анализаторы обычно легко распознают ошибки локального использования после устранения ошибок . Следовательно, этот подход менее полезен для локальных указателей и чаще используется с указателями, хранящимися в долгоживущих структурах. В целом, установка указателей на NULL- это хорошая практика, поскольку она позволяет программисту NULLпроверять указатели перед разыменованием, тем самым помогая предотвратить сбои.

Вспоминая пример с массивом, можно также создать массив фиксированного размера с помощью динамического распределения:

int (*a)[100] = malloc(sizeof *a);

... Что дает указатель на массив.

Доступ к указателю на массив можно осуществить двумя способами:

(*a)[index];

index[*a];

Итерацию также можно выполнить двумя способами:

for (int i = 0; i < 100; i++)
    (*a)[i];

for (int *i = a[0]; i < a[1]; i++)
    *i;

Преимущество использования второго примера заключается в том, что числовое ограничение первого примера не требуется, что означает, что указатель на массив может иметь любой размер, а второй пример может выполняться без каких-либо изменений.

Многомерные массивы

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

int array2d[ROWS][COLUMNS];

где ROWS и COLUMNS - константы. Это определяет двумерный массив. Если читать индексы слева направо, то array2d представляет собой массив длиной ROWS , каждый элемент которого представляет собой массив целых чисел COLUMNS .

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

array2d[4][3]

Опять же, читая слева направо, мы получаем доступ к 5-й строке и 4-му элементу в этой строке. Выражение array2d[4]представляет собой массив, который мы затем индексируем с помощью [3], чтобы получить доступ к четвертому целому числу.

Индексы массива против арифметики указателя
Элемент Первый Вторая строка, второй столбец i- я строка, j- й столбец
Индекс массива array[0][0] array[1][1] array[i - 1][j - 1]
Разыменованный указатель *(*(array + 0) + 0) *(*(array + 1) + 1) *(*(array + i - 1) + j - 1)

Аналогичным образом можно объявлять и многомерные массивы.

Не следует путать многомерный массив с массивом ссылок на массивы (также известный как векторы Илиффа или иногда массив массивов ). Первый всегда прямоугольный (все подмассивы должны быть одинакового размера) и занимает непрерывную область памяти. Последний представляет собой одномерный массив указателей, каждый из которых может указывать на первый элемент подмассива в другом месте в памяти, и подмассивы не обязательно должны быть одинакового размера. Последние могут быть созданы путем многократного использования malloc.

Струны

В C строковые литералы заключаются в двойные кавычки ( "), например "Hello world!", компилируются в массив указанных charзначений с дополнительным кодом завершающего нулевого символа ( с нулевым значением), чтобы отметить конец строки.

Строковые литералы не могут содержать встроенные символы новой строки; этот запрет несколько упрощает синтаксический анализ языка. Чтобы включить новую строку в строку, можно использовать обратную косую черту \n , как показано ниже.

Существует несколько стандартных библиотечных функций для работы со строковыми данными (не обязательно константами), организованными в виде массива с charиспользованием этого формата с завершающим нулем; см. ниже .

Синтаксис строкового литерала C оказал большое влияние и проник во многие другие языки, такие как C ++, Objective-C, Perl, Python, PHP, Java, Javascript, C #, Ruby. В настоящее время почти все новые языки используют строковый синтаксис в стиле C. Языки, в которых отсутствует этот синтаксис, как правило, предшествуют C.

Обратная косая черта экранируется

Если вы хотите включить двойные кавычки внутри строки, это можно сделать, экранировав ее с помощью обратной косой черты ( \), например "This string contains \"double quotes\".". Чтобы вставить буквальную обратную косую черту, нужно ее удвоить, например "A backslash looks like this: \\".

Для ввода управляющих символов и т. Д. В строку можно использовать обратную косую черту:

Побег Значение
\\ Буквальная обратная косая черта
\" Двойная кавычка
\' Одиночная цитата
\n Новая строка (перевод строки)
\r Возврат каретки
\b Backspace
\t Горизонтальная вкладка
\f Подача формы
\a Предупреждение (звонок)
\v Вертикальная табуляция
\? Вопросительный знак (используется для экранирования триграфов )
%% Знак процента, только строки формата printf (примечание \% нестандартно и не всегда распознается)
\OOO Знак с восьмеричным значением ООО (где ООО - это 1-3 восьмеричные цифры, '0' - '7')
\xHH Символ с шестнадцатеричным значением HH (где HH - это 1 или более шестнадцатеричных цифр, '0' - '9', 'A' - 'F', 'a' - 'f')

Использование других escape-символов обратной косой черты не определено стандартом C, хотя поставщики компиляторов часто предоставляют дополнительные escape-коды в качестве языковых расширений. Одна из них - это escape-последовательность \eдля escape-символа с шестнадцатеричным значением ASCII 1B, который не был добавлен в стандарт C из-за отсутствия представления в других наборах символов (например, EBCDIC ). Он доступен в GCC , clang и tcc .

Конкатенация строковых литералов

C имеет конкатенацию строковых литералов , что означает, что смежные строковые литералы объединяются во время компиляции; это позволяет разбивать длинные строки на несколько строк, а также позволяет добавлять строковые литералы, полученные в результате определений препроцессора C, и макросы к строкам во время компиляции:

    printf(__FILE__ ": %d: Hello "
           "world\n", __LINE__);

расширится до

    printf("helloworld.c" ": %d: Hello "
           "world\n", 10);

что синтаксически эквивалентно

    printf("helloworld.c: %d: Hello world\n", 10);

Символьные константы

Отдельные символьные константы заключаются в одинарные кавычки, например 'A', и имеют тип int(в C ++, char). Разница в том, что "A"представляет собой массив из двух символов с завершающим нулем, 'A' и '\ 0', тогда как 'A'непосредственно представляет символьное значение (65, если используется ASCII). Поддерживаются те же символы обратной косой черты, что и для строк, за исключением того, что (конечно) "можно корректно использовать в качестве символа без экранирования, тогда как 'теперь необходимо экранировать.

Символьная константа не может быть пустой (т. ''Е. Недопустимый синтаксис), хотя строка может быть пустой (она все еще имеет нулевой завершающий символ). Многосимвольные константы (например 'xy') допустимы, хотя редко используются - они позволяют хранить несколько символов в целом числе (например, 4 символа ASCII могут поместиться в 32-битное целое число, 8 - в 64-битное). Поскольку порядок, в котором символы упаковываются в intне определен (оставлено на усмотрение реализации), переносимое использование многосимвольных констант затруднено.

Тем не менее, в ситуациях, ограниченных конкретной платформой и реализацией компилятора, многосимвольные константы действительно находят свое применение при указании сигнатур. Одним из распространенных вариантов использования является OSType , где комбинация компиляторов Classic Mac OS и присущая ему обратная последовательность байтов означает, что байты целого числа появляются в точном порядке символов, определенных в литерале. Определение популярной «реализация», на самом деле соответствуют: в GCC, Clang и Visual C ++ , '1234'выходы под ASCII. 0x31323334

Строки широких символов

Поскольку тип charимеет ширину 1 байт, одно charзначение обычно может представлять не более 255 различных кодов символов, что недостаточно для всех символов, используемых во всем мире. Чтобы обеспечить лучшую поддержку международных символов, первый стандарт C (C89) представил широкие символы (закодированные в типе wchar_t) и строки широких символов, которые записываются какL"Hello world!"

Широкие символы обычно имеют 2 байта (с использованием 2-байтовой кодировки, такой как UTF-16 ) или 4 байта (обычно UTF-32 ), но в стандарте C ширина не указана wchar_t, оставляя выбор разработчику. Microsoft Windows обычно использует UTF-16, поэтому длина указанной выше строки для компилятора Microsoft составляет 26 байт; Unix , мир предпочитает UTF-32, таким образом , компиляторы , такие как GCC , будет генерировать строку 52 байт. На 2-байтовую ширину wchar_tнакладываются те же ограничения char, что и на некоторые символы (те, которые не входят в BMP ) не могут быть представлены в виде одного wchar_t; но должны быть представлены с помощью суррогатных пар .

Исходный стандарт C определял только минимальные функции для работы с широкими символьными строками; в 1995 году стандарт был изменен, чтобы включить гораздо более обширную поддержку, сравнимую с поддержкой charструнных. Соответствующие функции в основном названы в честь их charэквивалентов с добавлением «w» или заменой «str» на «wcs»; они указаны в <wchar.h>, <wctype.h>содержащем функции классификации и отображения широких символов.

В настоящее время обычно рекомендуемый метод поддержки международных символов - через UTF-8 , который хранится в charмассивах и может быть записан непосредственно в исходный код при использовании редактора UTF-8, поскольку UTF-8 является прямым расширением ASCII .

Струны переменной ширины

Распространенной альтернативой wchar_tявляется использование кодировки переменной ширины , при которой логический символ может занимать несколько позиций в строке. Строки переменной ширины могут быть дословно закодированы в литералы, рискуя запутать компилятор, или с использованием числовых символов обратной косой черты (например, "\xc3\xa9"для «é» в UTF-8). UTF-8 кодировка была специально разработана ( в соответствии с планом 9 ) для совместимости со стандартной библиотеки строковых функций; Поддерживающие функции кодирования включают в себя отсутствие встроенных нулей, правильную интерпретацию подпоследовательностей и тривиальную ресинхронизацию. Кодировки, в которых отсутствуют эти функции, могут оказаться несовместимыми со стандартными библиотечными функциями; В таких случаях часто используются строковые функции с поддержкой кодирования.

Библиотечные функции

Строками , как постоянными, так и переменными, можно управлять без использования стандартной библиотеки . Однако библиотека содержит множество полезных функций для работы со строками с завершающим нулем.

Структуры и союзы

Структуры

Структуры и объединения в C определяются как контейнеры данных, состоящие из последовательности именованных элементов различных типов. Они похожи на записи на других языках программирования. Члены структуры хранятся в последовательных местах в памяти, хотя компилятору разрешено вставлять отступы между элементами или после них (но не перед первым элементом) для эффективности или в качестве заполнения, необходимого для правильного выравнивания целевой архитектурой. Размер структуры равен сумме размеров ее членов плюс размер отступа.

Союзы

Объединения в C связаны со структурами и определяются как объекты, которые могут содержать (в разное время) объекты разных типов и размеров. Они аналогичны вариантным записям в других языках программирования. В отличие от структур, все компоненты объединения относятся к одному и тому же месту в памяти. Таким образом, объединение может использоваться в разное время для хранения разных типов объектов без необходимости создавать отдельный объект для каждого нового типа. Размер объединения равен размеру его самого большого типа компонента.

Декларация

Структуры объявляются с помощью structключевого слова, а объединения объявляются с помощью unionключевого слова. За ключевым словом спецификатора следует необязательное имя идентификатора, которое используется для идентификации формы структуры или объединения. За идентификатором следует объявление структуры или тела объединения: список объявлений членов, содержащихся в фигурных скобках, причем каждое объявление заканчивается точкой с запятой. Наконец, объявление завершается дополнительным списком имен идентификаторов, которые объявляются как экземпляры структуры или объединения.

Например, следующий оператор объявляет названную структуру s, содержащую три члена; он также объявит экземпляр структуры, известной как tee:

struct s {
    int   x;
    float y;
    char  *z;
} tee;

И следующий оператор объявит похожее объединение с именем uи именем его экземпляра n:

union u {
    int   x;
    float y;
    char  *z;
} n;

Члены структур и объединений не могут иметь неполный или функциональный тип. Таким образом, члены не могут быть экземпляром объявляемой структуры или объединения (потому что на этом этапе они неполны), но могут быть указателями на объявляемый тип.

После объявления структуры или тела объединения и присвоения им имени их можно рассматривать как новый тип данных, используя спецификатор structили union, при необходимости, и имя. Например, следующий оператор, учитывая указанное выше объявление структуры, объявляет новый экземпляр sназванной структуры r:

struct s r;

Также часто используется typedefспецификатор, чтобы исключить необходимость использования ключевого слова structили unionв последующих ссылках на структуру. Первый идентификатор после тела структуры принимается как новое имя для типа структуры (экземпляры структуры не могут быть объявлены в этом контексте). Например, следующий оператор объявит новый тип, известный как s_type, который будет содержать некоторую структуру:

typedef struct {...} s_type;

Затем будущие операторы могут использовать спецификатор s_type (вместо расширенного structспецификатора ...) для ссылки на структуру.

Доступ к участникам

Доступ к членам осуществляется с помощью имени экземпляра структуры или объединения, точки ( .) и имени члена. Например, учитывая объявление tee сверху, к члену, известному как y (типа float), можно получить доступ, используя следующий синтаксис:

tee.y

Доступ к структурам обычно осуществляется через указатели. Рассмотрим следующий пример, который определяет указатель на tee , известный как ptr_to_tee :

struct s *ptr_to_tee = &tee;

Затем можно получить доступ к члену y элемента tee путем разыменования ptr_to_tee и использования результата в качестве левого операнда:

(*ptr_to_tee).y

Это идентично более простому описаниюtee.y выше, если ptr_to_tee указывает на tee . Из-за приоритета оператора ("." Выше, чем "*"), более короткий не *ptr_to_tee.yподходит для этой цели, вместо этого он анализируется как *(ptr_to_tee.y)и, следовательно, круглые скобки необходимы. Поскольку это обычная операция, C предоставляет сокращенный синтаксис для доступа к члену непосредственно из указателя. В этом синтаксисе имя экземпляра заменяется именем указателя, а точка заменяется последовательностью символов ->. Таким образом, следующий метод доступа к y идентичен двум предыдущим:

ptr_to_tee->y

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

Это можно связать цепью; например, в связанном списке можно ссылаться n->next->nextна второй следующий узел (при условии, что n->nextон не равен нулю).

Назначение

Присвоение значений отдельным членам структур и объединений синтаксически идентично присвоению значений любому другому объекту. Единственное отличие состоит в том, что lvalue присвоения - это имя члена, доступ к которому осуществляется с помощью синтаксиса, упомянутого выше.

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

Например, следующий оператор присваивает значение 74 (кодовая точка ASCII для буквы 't') члену с именем x в структуре tee сверху:

tee.x = 74;

И то же самое назначение с использованием ptr_to_tee вместо tee будет выглядеть так:

ptr_to_tee->x = 74;

Назначение с членами профсоюзов идентично.

Прочие операции

Согласно стандарту C единственные допустимые операции, которые могут выполняться над структурой, - это ее копирование, присвоение ей как единицы (или ее инициализация), получение ее адреса с помощью &унарного оператора address-of ( ) и доступ к ее членам. . У профсоюзов такие же ограничения. Одна из операций неявно запретных является сравнение: структуры и объединения не могут быть сравнены с использованием стандартных средств сравнения языка C ( ==, >, <и т.д.).

Битовые поля

C также предоставляет особый тип элемента структуры, известный как битовое поле , которое представляет собой целое число с явно указанным количеством битов. Битовое поле объявляется как член структуры типа int, signed int, unsigned int, или _Bool, следуя имя элемента двоеточием ( :) , и число битов , он должен занимать. Общее количество битов в одном битовом поле не должно превышать общее количество бит в его объявленном типе.

В качестве специального исключения из обычных правил синтаксиса C определяется реализацией, является ли битовое поле, объявленное как тип int, без указания signedили unsigned, со знаком или без знака. Таким образом, рекомендуется явно указать signedили unsignedна всех элементах структуры для переносимости.

Также разрешены безымянные поля, состоящие только из двоеточия, за которым следует несколько бит; они указывают на заполнение . Указание нулевой ширины для безымянного поля используется для принудительного выравнивания по новому слову.

Члены битовых полей не имеют адресов и, как таковые, не могут использоваться с &унарным оператором address-of ( ). sizeofОператор не может быть применен к битовым полям.

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

struct f {
    unsigned int  flag : 1;  /* a bit flag: can either be on (1) or off (0) */
    signed int    num  : 4;  /* a signed 4-bit field; range -7...7 or -8...7 */
    signed int         : 3;  /* 3 bits of padding to round out to 8 bits */
} g;

Инициализация

Инициализация по умолчанию зависит от спецификатора класса хранения , описанного выше.

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

int x = 12;
int y = { 23 };     //Legal, no warning
int z = { { 34 } }; //Legal, expect a warning

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

Следующий оператор инициализирует новый экземпляр структуры S , известную как пи :

struct s {
    int   x;
    float y;
    char  *z;
};

struct s pi = { 3, 3.1415, "Pi" };

Назначенные инициализаторы

Назначенные инициализаторы позволяют инициализировать элементы по имени в любом порядке и без явного указания предшествующих значений. Следующая инициализация эквивалентна предыдущей:

struct s pi = { .z = "Pi", .x = 3, .y = 3.1415 };

Использование обозначения в инициализаторе перемещает «курсор» инициализации. В приведенном ниже примере, если MAXоно больше 10, в середине будут элементы с нулевым значением a; если он меньше 10, некоторые из значений, предоставленных первыми пятью инициализаторами, будут переопределены вторыми пятью (если MAXменьше 5, возникнет ошибка компиляции):

int a[MAX] = { 1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0 };

В C89 объединение было инициализировано одним значением, примененным к его первому члену. То есть для указанного выше объединения u может быть инициализирован только член int x :

union u value = { 3 };

При использовании назначенного инициализатора инициализируемый член не обязательно должен быть первым членом:

union u value = { .y = 3.1415 };

Если размер массива неизвестен (т.е. массив был неполного типа ), количество инициализаторов определяет размер массива, и его тип становится полным:

int x[] = { 0, 1, 2 } ;

Составные указатели могут использоваться для обеспечения явной инициализации, когда неприкрашенные списки инициализаторов могут быть неправильно поняты. В приведенном ниже примере wобъявлен как массив структур, каждая структура состоит из члена a(массив из 3 int) и члена b(an int). Инициализатор устанавливает размер равным w2 и устанавливает значения первого элемента каждого a:

struct { int a[3], b; } w[] = { [0].a = {1}, [1].a[0] = 2 };

Это эквивалентно:

struct { int a[3], b; } w[] =
{
   { { 1, 0, 0 }, 0 },
   { { 2, 0, 0 }, 0 } 
};

В стандартном C. нет возможности указать повторение инициализатора.

Составные литералы

Можно позаимствовать методологию инициализации для генерации составной структуры и литералов массива:

// pointer created from array literal.
int *ptr = (int[]){ 10, 20, 30, 40 };

// pointer to array.
float (*foo)[3] = &(float[]){ 0.5f, 1.f, -0.5f };

struct s pi = (struct s){ 3, 3.1415, "Pi" };

Составные литералы часто объединяются с назначенными инициализаторами, чтобы сделать объявление более читаемым:

pi = (struct s){ .z = "Pi", .x = 3, .y = 3.1415 };

Операторы

Структуры управления

C - это язык свободной формы .

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

Составные заявления

В элементах этого раздела любой <statement> можно заменить составным оператором . Составные утверждения имеют форму:

{
    <optional-declaration-list>
    <optional-statement-list>
}

и используются как тело функции или в любом месте, где ожидается одиночный оператор. Список-деклараций объявляет переменные, которые будут использоваться в этой области , а список-операторов - действия, которые необходимо выполнить. Скобки определяют свою собственную область видимости, и переменные, определенные внутри этих скобок, будут автоматически освобождены в закрывающей скобке. Объявления и операторы можно свободно смешивать в составном операторе (как в C ++ ).

Заявления о выборе

В C есть два типа операторов выбора : ifоператор и switchоператор .

ifУтверждение в виде:

if (<expression>)
    <statement1>
else
    <statement2>

В ifоператоре, если значение <expression>в круглых скобках не равно нулю (истина), управление переходит к <statement1>. Если elseпредложение присутствует и значение <expression>равно нулю (ложь), управление перейдет к <statement2>. Эта else <statement2>часть является необязательной, и, если она отсутствует, false <expression>просто приведет к пропуску <statement1>. elseВсегда совпадает с ближайшим предыдущим непревзойденным if; фигурные скобки могут использоваться для отмены этого при необходимости или для ясности.

В switchзаявлении причины управления должны быть переданы одному из нескольких операторов в зависимости от значения в выражение , которое должно иметь целочисленный тип . Подразделение, управляемое переключателем, обычно является составным. Любой оператор в подвыложении может быть помечен одной или несколькими caseметками, которые состоят из ключевого слова, caseза которым следует постоянное выражение, а затем двоеточие (:). Синтаксис следующий:

switch (<expression>)
{
    case <label1> :
        <statements 1>
    case <label2> :
        <statements 2>
        break;
    default :
        <statements 3>
}

Никакие две константы case, связанные с одним и тем же переключателем, не могут иметь одинаковое значение. С defaultпереключателем может быть связано не более одной метки. Если ни одна из меток case не равна выражению в следующих скобках switch, управление передается defaultметке или, если defaultметки нет , выполнение возобновляется сразу за пределами всей конструкции.

Переключатели могут быть вложенными; caseили defaultметка связана с сокровенным , switchкоторый его содержит. Операторы Switch могут «проваливаться», то есть, когда один раздел case завершил свое выполнение, операторы будут продолжать выполняться вниз, пока break;не встретится оператор. Падение полезно при некоторых обстоятельствах, но обычно нежелательно. В предыдущем примере, если <label2>достигнуто, <statements 2>выполняются операторы и больше ничего внутри фигурных скобок. Однако, если <label1>достигается, оба <statements 1>и <statements 2>выполняются, поскольку нет breakразделения двух операторов case.

Возможно, хотя и необычно, вставлять switchметки в подблоки других структур управления. Примеры этого включают устройство Даффа и реализацию сопрограмм Саймона Тэтэма в Putty .

Операторы итерации

В C есть три формы итерационного оператора:

do
    <statement>
while ( <expression> ) ;

while ( <expression> )
    <statement>

for ( <expression> ; <expression> ; <expression> )
    <statement>

В операторах whileи doподвыражение выполняется многократно, пока значение expressionне равно нулю (эквивалентно истине). С while, тест, включая все побочные эффекты из <expression>, происходит перед каждой итерацией (выполнением <statement>); с do, проверка выполняется после каждой итерации. Таким образом, doоператор всегда выполняет свой подоператор, по крайней мере, один раз, в то время как он whileможет не выполняться вообще.

Заявление:

for (e1; e2; e3)
    s;

эквивалентно:

e1;
while (e2)
{
    s;
cont:
    e3;
}

за исключением поведения continue;оператора (который в forцикле переходит к e3вместо e2). Если e2поле пусто, его необходимо заменить на 1.

Любое из трех выражений в forцикле может быть опущено. Отсутствие второго выражения делает whileтест всегда ненулевым, создавая потенциально бесконечный цикл.

Начиная с C99 , первое выражение может принимать форму объявления, обычно включающего инициализатор, например:

for (int i = 0; i < limit; ++i) {
    // ...
}

Объем объявления ограничен размером forцикла.

Заявления о прыжках

Операторы перехода безоговорочно передают управление. Есть четыре типа операторов перехода в C: goto, continue, break, и return.

gotoЗаявление выглядит следующим образом :

goto <identifier> ;

Идентификатор должен быть ярлык ( с последующим двоеточием) , расположенной в текущей функции. Управление переходит к помеченной выписке.

continueУтверждение может появиться только в пределах заявления итерации и причины управления , чтобы перейти к петлевому продолжению части самых внутреннему заключающей итерации утверждения. То есть в каждом из утверждений

while (expression)
{
    /* ... */
    cont: ;
}

do
{
    /* ... */
    cont: ;
} while (expression);

for (expr1; expr2; expr3) {
     /* ... */
     cont: ;
}

a, continueне содержащийся во вложенном операторе итерации, совпадает с goto cont.

Оператор breakиспользуется для завершения forцикла, whileцикла, doцикла или switchоператора. Управление передается оператору, следующему за завершенным оператором.

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

Сохранение адреса этикетки

GCC расширяет язык C с помощью унарного &&оператора, который возвращает адрес метки. Этот адрес может быть сохранен в виде void*переменной и может использоваться позже в gotoинструкции. Например, следующая печать "hi "в бесконечном цикле:

    void *ptr = &&J1;

J1: printf("hi ");
    goto *ptr;

Эта функция может использоваться для реализации таблицы переходов .

Функции

Синтаксис

Определение функции AC состоит из типа возвращаемого значения ( voidесли значение не возвращается), уникального имени, списка параметров в круглых скобках и различных операторов:

<return-type> functionName( <parameter-list> )
{
    <statements>
    return <expression of type return-type>;
}

Функция с voidневозвратным типом должна включать хотя бы один returnоператор. Параметры задаются <parameter-list>, разделенный запятыми список объявлений параметров, каждый элемент в списке , являющегося типом данных с последующим идентификатором: <data-type> <variable-identifier>, <data-type> <variable-identifier>, ....

Если параметров нет, <parameter-list>можно оставить пустым или при желании указать одно слово void.

Можно определить функцию как принимающую переменное количество параметров, указав ...ключевое слово в качестве последнего параметра вместо типа данных и идентификатора переменной. Часто используемая функция, которая делает это, - это стандартная библиотечная функция printf, которая имеет объявление:

int printf (const char*, ...);

Манипулировать этими параметрами можно с помощью подпрограмм в заголовке стандартной библиотеки <stdarg.h>.

Указатели функций

Указатель на функцию можно объявить следующим образом:

<return-type> (*<function-name>)(<parameter-list>);

Следующая программа показывает использование указателя функции для выбора между сложением и вычитанием:

#include <stdio.h>

int (*operation)(int x, int y);

int add(int x, int y)
{
    return x + y;
}

int subtract(int x, int y)
{
    return x - y;
}

int main(int argc, char* args[])
{
   int  foo = 1, bar = 1;

   operation = add;
   printf("%d + %d = %d\n", foo, bar, operation(foo, bar));
   operation = subtract;
   printf("%d - %d = %d\n", foo, bar, operation(foo, bar));
   return 0;
}

Глобальная структура

После предварительной обработки на самом высоком уровне программа C состоит из последовательности объявлений в области видимости файла. Они могут быть разделены на несколько отдельных исходных файлов, которые могут быть скомпилированы отдельно; полученные объектные модули затем связываются вместе с модулями поддержки времени выполнения, обеспечиваемыми реализацией, для создания исполняемого образа.

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

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

Реализация C, обеспечивающая все функции стандартной библиотеки, называется размещенной реализацией . Программы, написанные для размещенных реализаций, должны определять специальную вызываемую функцию main, которая является первой функцией, вызываемой при запуске программы.

Размещенные реализации запускают выполнение программы с вызова mainфункции, которая должна быть определена в соответствии с одним из этих прототипов:

int main() {...}
int main(void) {...}
int main(int argc, char *argv[]) {...}
int main(int argc, char **argv) {...}

Первые два определения эквивалентны (и оба совместимы с C ++). Выбор одного из них, вероятно, зависит от индивидуальных предпочтений (текущий стандарт C содержит два примера main()и два из них main(void), но черновик стандарта C ++ использует main()). Возвращаемое значение main(которое должно быть int) служит статусом завершения, возвращаемым в среду хоста.

Стандарт C определяет возвращаемые значения 0и EXIT_SUCCESSкак указывающие на успех, и EXIT_FAILUREкак на отказ. ( EXIT_SUCCESSи EXIT_FAILUREопределены в <stdlib.h>). Другие возвращаемые значения имеют значение, определяемое реализацией; например, в Linux программа, завершенная сигналом, дает код возврата, состоящий из числового значения сигнала плюс 128.

Минимальная правильная программа на C состоит из пустой mainпроцедуры, не принимающей аргументов и ничего не делающей:

int main(void){}

Поскольку returnоператора нет, mainпри выходе возвращает 0. (Это особая функция, представленная в C99, которая применяется только к main.)

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

Некоторые реализации не размещаются, обычно потому, что они не предназначены для использования с операционной системой . Такие реализации называются автономными в стандарте C. Отдельная реализация может свободно определять, как она обрабатывает запуск программы; в частности, для определения mainфункции не требуется программа .

Функции могут быть написаны программистом или предоставлены существующими библиотеками. Интерфейсы для последних обычно объявляются путем включения файлов заголовков - с помощью #include директивы предварительной обработки - и объекты библиотеки связываются в окончательный исполняемый образ. Некоторые библиотечные функции, такие как printf, определены стандартом C; они называются стандартными библиотечными функциями.

Функция может возвращать значение вызывающей стороне (обычно другая функция C или среда размещения для функции main). printfФункция упоминалась выше доходностей , как были напечатаны много символов, но это значение часто игнорируются.

Передача аргумента

В C аргументы передаются функциям по значению, в то время как другие языки могут передавать переменные по ссылке . Это означает, что принимающая функция получает копии значений и не имеет прямого способа изменить исходные переменные. Чтобы функция изменила переменную, переданную из другой функции, вызывающий должен передать свой адрес ( указатель на него), который затем можно разыменовать в принимающей функции. См. Указатели для получения дополнительной информации.

void incInt(int *y)
{
    (*y)++;  // Increase the value of 'x', in 'main' below, by one
}

int main(void)
{
    int x = 0;
    incInt(&x);  // pass a reference to the var 'x'
    return 0;
}

Аналогично работает функция scanf :

int x;
scanf("%d", &x);

Чтобы передать редактируемый указатель на функцию (например, с целью возврата выделенного массива в вызывающий код), вы должны передать указатель на этот указатель: его адрес.

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

void allocate_array(int ** const a_p, const int A) {
/* 
 allocate array of A ints
 assigning to *a_p alters the 'a' in main()
*/
    *a_p = malloc(sizeof(int) * A); 
}

int main(void) {
    int * a; /* create a pointer to one or more ints, this will be the array */

 /* pass the address of 'a' */
    allocate_array(&a, 42);

/* 'a' is now an array of length 42 and can be manipulated and freed here */

    free(a);
    return 0;
}

Параметр int **a_pпредставляет собой указатель на указатель на объект int, который в данном случае является адресом указателя, pопределенного в основной функции.

Параметры массива

Функциональные параметры типа массива могут на первый взгляд показаться исключением из правила передачи по значению языка Си. Следующая программа напечатает 2, а не 1:

#include <stdio.h>

void setArray(int array[], int index, int value)
{
    array[index] = value;
}

int main(void)
{
    int a[1] = {1};
    setArray(a, 0, 2);
    printf ("a[0]=%d\n", a[0]);
    return 0;
}

Однако есть другая причина такого поведения. Фактически, параметр функции, объявленный с типом массива, рассматривается как параметр, объявленный как указатель. То есть предыдущее объявление setArrayэквивалентно следующему:

void setArray(int *array, int index, int value)

В то же время правила C для использования массивов в выражениях приводят к преобразованию значения aв вызове в setArrayуказатель на первый элемент массива a. Таким образом, на самом деле это все еще пример передачи по значению с оговоркой, что это адрес первого элемента массива, передаваемого по значению, а не содержимое массива.

Разнообразный

Зарезервированные ключевые слова

Следующие слова зарезервированы и не могут использоваться в качестве идентификаторов:

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

Чувствительность к регистру

Идентификаторы C чувствительны к регистру (например, foo, FOO, и Fooимена различных объектов). Некоторые компоновщики могут сопоставлять внешние идентификаторы с одним случаем, хотя в большинстве современных компоновщиков это нечасто.

Комментарии

Текст, начинающийся с токена /* , рассматривается как комментарий и игнорируется. Комментарий заканчивается следующим */; это может происходить внутри выражений и может занимать несколько строк. Случайное пропускание признака конца комментария проблематично, поскольку правильно построенный признак конца комментария следующего комментария будет использоваться для завершения начального комментария, а весь код между комментариями будет считаться комментарием. Комментарии в стиле C не вкладываются; то есть случайное размещение комментария в комментарии приводит к непредвиденным результатам:

/*
This line will be ignored.
/*
A compiler warning may be produced here. These lines will also be ignored.
The comment opening token above did not start a new comment,
and the comment closing token below will close the comment begun on line 1.
*/
This line and the line below it will not be ignored. Both will likely produce compile errors.
*/

Комментарии к строке в стиле C ++ начинаются с //конца строки и простираются до ее конца. Этот стиль комментариев возник в BCPL и стал допустимым синтаксисом C в C99 ; он не доступен ни в оригинальном K&R C, ни в ANSI C :

// this line will be ignored by the compiler

/* these lines
   will be ignored
   by the compiler */

x = *p/*q;  /* this comment starts after the 'p' */

Аргументы командной строки

В параметрах , приведенные в командной строке , передаются в программу C с двумя предопределенными переменными - подсчетом аргументов командной строки в argcи отдельных аргументах как строки символов в массиве указателей argv. Итак, команда:

myFilt p1 p2 p3

приводит к чему-то вроде:

м у F я л т \ 0 п 1 \ 0 п 2 \ 0 п 3 \ 0
argv [0] argv [1] argv [2] argv [3]

Хотя отдельные строки представляют собой массивы смежных символов, нет гарантии, что строки хранятся как непрерывная группа.

Имя программы argv[0]может быть полезно при печати диагностических сообщений или для использования одного двоичного файла в нескольких целях. Отдельные значения параметров могут быть доступны с argv[1], argv[2]и argv[3], как показано в следующей программе:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("argc\t= %d\n", argc);
    for (int i = 0; i < argc; i++)
        printf("argv[%i]\t= %s\n", i, argv[i]);
}

Порядок оценки

В любом достаточно сложном выражении, возникает выбор , как в порядке , в котором для оценки части выражения: могут быть оценены в порядке , , , , или в порядке , , , . Формально соответствующий компилятор C может оценивать выражения в любом порядке между точками последовательности (это позволяет компилятору выполнить некоторую оптимизацию). Точки последовательности определяются: (1+1)+(3+3)(1+1)+(3+3)(2)+(3+3)(2)+(6)(8)(1+1)+(3+3)(1+1)+(6)(2)+(6)(8)

  • Заявление заканчивается точкой с запятой.
  • Оператор последовательности : запятая. Однако запятые, разделяющие аргументы функции, не являются точками последовательности.
  • Операторы короткого замыкания : логическое and ( &&которое может быть прочитано и затем ) и логическое or ( ||которое может быть прочитано или иначе ).
  • Тройной оператор ( ?:): Этот оператор оценивает свое первое суб-выражение, а затем его второго или третьего (никогда оба из них) , на основе значения первого.
  • Вход и выход из вызова функции (но не между оценками аргументов).

Выражения перед точкой последовательности всегда оцениваются перед выражениями после точки последовательности. В случае оценки короткого замыкания второе выражение может не оцениваться в зависимости от результата первого выражения. Например, в выражении , если первый аргумент имеет ненулевое значение (истина), результатом всего выражения не может быть ничего, кроме истины, поэтому он не оценивается. Точно так же в выражении , если первый аргумент равен нулю (ложь), результатом всего выражения не может быть ничего, кроме ложного, поэтому он не оценивается. (a() || b())b()(a() && b())b()

Аргументы вызова функции могут оцениваться в любом порядке, если все они оцениваются к моменту ввода функции. Следующее выражение, например, имеет неопределенное поведение:

 printf("%s %s\n", argv[i = 0], argv[++i]);

Неопределенное поведение

Аспект стандарта C (не уникальный для C) заключается в том, что поведение определенного кода называется «неопределенным». На практике это означает, что программа, созданная из этого кода, может делать все, что угодно, от работы по замыслу программиста до сбоев при каждом запуске.

Например, следующий код производит неопределенное поведение, потому что переменная b изменяется более одного раза без промежуточной точки последовательности:

#include <stdio.h>

int main(void)
{
    int b = 1;
    int a = b++ + b++;
    printf("%d\n", a);
}

Поскольку между модификациями b в « b ++ + b ++» нет точки последовательности , можно выполнять шаги оценки более чем в одном порядке, что приводит к неоднозначному утверждению. Это можно исправить, переписав код, чтобы вставить точку последовательности, чтобы обеспечить однозначное поведение, например:

a = b++;
a += b++;

Смотрите также

использованная литература

Общий

внешние ссылки