Язык C в вопросах и ответах. Часть 2 (FAQ)

Источник: hardline

 

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

О:      Правило, по которому массивы превращаются в указатели не может
        применяться рекурсивно. Массив массивов (т.е. двумерный массив в С)
превращается в указатель на массив, а не в указатель на указатель.
Указатели на массивы могут вводить в заблуждение и применять их нужно
        с осторожностью. (Путаница еще более усугубляется тем, что существуют
        некорректные компиляторы, включая некоторые версии pcc и полученные
        на основе pcc программы lint, которые неверно вопринимают присваивание
        многоуровневым указателям многомерных массивов.) Если вы передаете
        двумерный массив функции:

int array[NROWS][NCOLUMNS];
f(array);

описание функции должно соответствовать

f(int a[][NCOLUMNS]) {...}
или

                 f(int (*ap)[NCOLUMNS]) {...}   /* ap - указатель на массив */

        В случае, когда используется первое описание, компилятор неявно
        осуществляет обычное преобразование  "массива массивов" в "указатель
        на массив"; во втором случае указатель на массив задается явно.
        Так как вызываемая функция не выделяет место для массива, нет
        необходимости знать  его размер, так что количество "строк" NROWS
        может быть опущено.  "Форма" массива по-прежнему важна, так что
        размер "столбца" NCOLUMNS должен быть включен (а для массивов
        размерности 3 и больше, все промежуточные размеры).

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

        Смотри: K&R I Разд.5.10 c. 110; K&R II Разд.5.9 c. 113.

2.11:   Как писать функции, принимающие в качестве параметра двумерные
        массивы, "ширина" которых во время компиляции неизвестна?

О: Это непросто. Один из путей - передать указатель на элемент [0][0]
вместе с размерами и затем симулировать индексацию "вручную":

f2(aryp, nrows, ncolumns)
int *aryp;
int nrows, ncolumns;
                { ... array[i][j] это aryp[i * ncolumns + j] ... }

        Этой функции массив из вопроса 2.10 может быть передан так:

f2(&array[0][0], NROWS, NCOLUMNS);

Нужно, однако, заметить, что программа, выполняющая индексирование
многомерного массива "вручную" не полностью соответствует стандарту
        ANSI C; поведение (&array[0][0])[x] не определено при x> NCOLUMNS.

gcc разрешает объявлять локальные массивы, которые имеют размеры,
задаваемые аргументами функции, но это - нестандартное расширение.
См. также вопрос 2.15.

2.12:   Как объявить указатель на массив?

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

        Вместо указателя на массив рассмотрим использование указателя на один
        из элементов массива.  Массивы типа T превращаются в указатели типа Т
        (см. вопрос 2.3), что удобно; индексация или увеличение указателя
        позволяет иметь доступ к отдельным элементам массива. Истинные
        указатели на массивы при увеличении или индексации указывают на
        следующий массив и в общем случае если и полезны, то лишь при
        операциях с массивами массивов. (Cм. вопрос 2.10 выше.)

        Если действительно нужно объявить указатель на целый массив,
используйте что-то вроде  "int (*ap)[N];" где N - размер массива.
(Cм. также вопрос 10.4.)  Если размер массива неизвестен, параметр
N может быть опущен, но получившийся в результате тип " указатель
        на массив неизвестного размера" - бесполезен.

2.13    Исходя из того, что ссылки на массив превращаются в указатели,
        скажите в чем разница для массива

int array[NROWS][NCOLUMNS];

        между array и &array?

O:      Согласно ANSI/ISO стандарту C, &array дает указатель типа
        "указатель-на-массив-Т", на весь массив (Cм. также вопрос 2.12).
        В языке C до выхода стандарта ANSI оператор & в &array игнорировался,
        порождая предупреждение компилятора. Все компиляторы C, встречая
        просто имя массива, порождают указатель типа указатель-на-Т, т.е. на
        первый элемент массива. (Cм. также вопрос 2.3.)

2.14:   Как динамически выделить память для многомерного массива?

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

                int **array1 = (int **)malloc(nrows * sizeof(int *));
                for(i = 0; i < nrows; i++)
                     array1[i] = (int *)malloc(ncolumns * sizeof(int));

(В "реальной" программе, malloc должна быть правильно объявлена, а
каждое возвращаемое malloc значение - проверено.)

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

                int **array2 = (int **)malloc(nrows * sizeof(int *));
array2[0] = (int *)malloc(nrows * ncolumns * sizeof(int));
for(i = 1; i < nrows; i++)
array2[i] = array2[0] + i * ncolumns;

        В любом случае  доступ к элементам динамически задаваемого массива
может быть произведен с помощью обычной индексации: array[i][j].

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

                int *array3 = (int *)malloc(nrows * ncolumns * sizeof(int));

Теперь, однако, операции индексирования нужно выполнять вручную,
осуществляя доступ к элементу i,j с помощью array3[i*ncolumns+j].
(Реальные вычисления можно скрыть в макросе, однако вызов макроса
требует круглых скобок и запятых, которые не выглядят в точности
так, как индексы многомерного массива.)

Наконец, можно использовать указатели на массивы:

int (*array4)[NCOLUMNS] =
                      (int(*)[NCOLUMNS])malloc(nrows * sizeof(*array4));,

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

        Пользуясь описанными приемами, необходимо освобождать память,
        занимаемую массивами (это может проходить в несколько шагов; см.
        вопрос 3.9), когда они больше не нужны, и не следует смешивать
        динамически создаваемые массивы с обычными, статическими (cм. вопрос
        2.15 ниже, а также вопрос 2.10).

2.15 Как мне равноправно использовать статически и динамически задаваемые
многомерные массивы при передаче их в качестве параметров функциям?

О: Идеального решения не существует. Возьмем объявления

int array[NROWS][NCOLUMNS];
int **array1;
int **array2;
int *array3;
int (*array4)[NCOLUMNS];

соответствующие способам выделения памяти в вопросах 2.10 и 2.14,
и функции, объявленные как

                f1(int a[][NCOLUMNS], int m, int n);
                f2(int *aryp, int nrows, int ncolumns);
f3(int **pp, int m, int n);

(см. вопросы 2.10 и 2.11). Тогда следующие вызовы должны работать
так, как ожидается

f1(array, NROWS, NCOLUMNS);
f1(array4, nrows, NCOLUMNS);
f2(&array[0][0], NROWS, NCOLUMNS);
f2(*array2, nrows, ncolumns);
f2(array3, nrows, ncolumns);
f2(*array4, nrows, NCOLUMNS);
f3(array1, nrows, ncolumns);
f3(array2, nrows, ncolumns);

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

f1((int (*)[NCOLUMNS])(*array2), nrows, ncolumns);
                f1((int (*)[NCOLUMNS])array3, nrows, ncolumns);

Необходимо еще раз отметить, что передача &array[0][0] функции f2
не совсем соответствует стандарту; см. вопрос 2.11.

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

2.16 Вот изящный трюк: если я пишу

int realarray[10];
int *array = &realarray[-1];,

то теперь можно рассматривать "array" как массив, у которого индекс
первого элемента равен единице.

О:      Хотя этот прием внешне привлекателен (и использовался в старых
        изданиях книги "Numerical Recipes in С"), он не удовлетворяет
        стандартам С. Арифметические действия над указателями определены лишь
        тогда, когда указатель ссылается на выделенный блок памяти или на
        воображаемый завершающий элемент, следующий сразу за блоком. В
        противном случае поведение программы не определено, _даже если
        указатель не переназначается_. Код, приведенный выше, плох тем,
        что при уменьшении смещения может быть получен неверный адрес
        (возможно, из-за циклического перехода адреса при пересечении
        границы сегмента).

        Смотри: ANSI Разд.3.3.6 c. 48, Rationale Разд.3.2.2.3 c. 38;
        K&R II  Разд.5.3 c. 100, Разд.5.4 c. 102-3, Разд.A7.7 c. 205-6.

2.17: Я передаю функции указатель, который она инициализирует

...
int *ip;
f(ip);
...

void f(ip)
int *ip;
{
static int dummy = 5;
ip = &dummy;
}

но указатель после вызова функции остается неизменным.

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

2.18 У меня определен указатель на char, который указывает еще и на int,
причем мне необходимо переходить к следующему элементу типа int.
Почему

                ((int *)p)++;

не работает?

О:      В языке С оператор преобразования типа не означает "будем действовать
так, как будто эти биты имеют другой тип"; это оператор, который
        действительно выполняет преобразования, причем по определению
        получается значение типа rvalue, которому нельзя присвоить новое
        значение и к которому не применим оператор ++. (Следует считать
        аномалией то, что компиляторы pcc и  расширения gcc вообще
        воспринимают выражения приведенного выше типа.).
        Скажите то, что думаете:

p = (char *)((int *)p + 1);

или просто

p += sizeof(int);

        Смотри: ANSI Разд.3.3.4, Rationale Разд.3.3.2.4 c. 43.

2.19 Могу я использовать void ** , чтобы передать функции по ссылке
обобщенный указатель?

О:      Стандартного решения не существует, поскольку в С нет общего типа
        указатель-на-указатель.  void * выступает в роли обобщенного
        указателя только потому, что автоматически осуществляются
        преобразования в ту и другую сторону, когда встречаются разные типы
        указателей. Эти преобразования не могут быть выполнены (истинный
        тип указателя неизвестен), если осуществляется попытка косвенной
        адресации, когда void ** указывает на что-то отличное от void *.

3. Выделение памяти

3.1: Почему не работает фрагмент кода?

char *answer;
printf("Type something:\n");
gets(answer);
printf("You typed \"%s\"\n", answer);

О: Указатель "answer", который передается функции gets как место, в
котором должны храниться вводимые символы, не инициализирован, т.е.
        не указывает на какое-то выделенное место. Иными словами, нельзя
        сказать, на что указывает "answer". (Так как локальные переменные не
        инициализируются, они вначале обычно содержат "мусор", то есть даже
        не гарантируется, что в начале "answer" - это нулевой указатель.
        См. вопрос 17.1).

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

#include <string.h>

char answer[100], *p;
printf("Type something:\n");
                fgets(answer, sizeof(answer), stdin);
                if((p = strchr(answer, '\n')) != NULL)
*p = '\0';
printf("You typed \"%s\"\n", answer);

Заметьте, что в этом примере  используется fgets() вместо gets()
(это признак хорошего тона, см. вопрос 11.6), что позволяет указать
размер массива, так что выход за пределы массива, когда пользователь
        введет слишком длинную строку, становится невозможным. (К сожалению,
        fgets() не удаляет автоматически завершающий символ конца строки \n,
        как это делает gets()).  Для выделения памяти можно также
        использовать malloc().

3.2: Не могу заставить работать strcat. В моей программе

char *s1 = "Hello, ";
char *s2 = "world!";
                char *s3 = strcat(s1, s2);

но результаты весьма странные.

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

Функция strcat не выделяет память; вторая строка присоединяется к
первой. Следовательно, одно из исправлений - в задании первой строки
в виде массива достаточной длины

char s1[20] = "Hello, ";

Так как strcat возвращает указатель на первую строку (в нашем случае
s1), переменная s3 - лишняя.

        Смотри: CT&P Разд. 3.2 c. 32.

3.3     Но в справочнике о функции  strcat сказано, что она  использует в
        качестве аргументов два указателя на char. Откуда мне знать о
        выделении памяти?

  О: Как правило, при использовании указателей _всегда_  необходимо
        иметь в виду выделение памяти, по крайней мере, быть уверенным,
        что компилятор делает это для Вас. Если в документации на
        библиотечную функцию явно ничего не сказано о выделении памяти,
то обычно это проблема вызывающей функции.

Краткое описание функции в верхней  части страницы справочника в
        стиле UNIX может ввести в заблуждение. Приведенные там фрагменты
        кода ближе к определению, необходимому для разработчика функции,
        чем для того, кто будет эту функцию вызывать. В частности, многие
функции, имеющие в качестве параметров указатели (на структуры или
стринги, например), обычно вызываются с параметрами, равными адресам
        каких-то уже существующих объектов( структур или массивов - см.
        вопросы 2.3, 2.4.) Другой распространенный пример - функция stat().

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

О:      Убедитесь, что правильно выделена область памяти, указатель на
        которую возвращает Ваша функция. Функция должна возвращать указатель
        на статически выделеннную область памяти или на буфер, передаваемый
        функции  в качестве параметра, или на память, выделенную с  помощью
        malloc(), но _не_ на локальный (auto) массив.  Другими  словами,
        никогда никогда не делайте ничего похожего на

char *f()
{
char buf[10];
/* ... */
return buf;
}

        Приведем одну поправку (непригодную в случае, когда f() вызывается
        рекурсивно, или когда одновременно нужны несколько возвращаемых
        значений)

                static char buf[10];

См.  также вопрос 17.5.

3.5 Почему в некоторых исходных текстах значения, возвращаемые  malloc(),
аккуратно преобразуются в указатели на выделяемый тип памяти?

О: До того как стандарт ANSI/ISO ввел обобщенный тип указателя void *,
        эти преобразования были обычно необходимы для подавления
        предупреждений компилятора о приравнивании указателей разных типов.
        (Согласно стандарту C ANSI/ISO, такие преобразования типов
        указателей не требуются).

3.6 Можно использовать содержимое динамически выделяемой памяти после
того как она освобождена?

О:      Нет. Иногда в старых описаниях malloc()  говорилось, что содержимое
        освобожденной памяти "остается неизменным"; такого рода поспешная
        гарантия никогда не была универсальной и не требуется стандартом ANSI.

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

struct list *listp, *nextp;
for(listp = base; listp != NULL; listp = nextp) {
nextp = listp->next;
free((char *)listp);
}

        и подумайте, что получится, если будет использовано на первый взгляд
        более очевидное выражение для тела цикла listp = listp->next, без
        временного указателя nextp.

        См.: ANSI Rationale Разд. 4.10.3.2 c. 102; CT&P Разд. 7.10 c. 95.

3.7     Откуда free() знает, сколько байт освобождать?

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

3.8     А могу я узнать действительный размер выделяемого блока?

О: Нет универсального ответа.

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

О:      Да. В общем, необходимо сделать так, чтобы каждый указатель,
        возвращаемый  malloc() был передан free() точно один раз (если память
        освобождается).

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

О: Большинство реализаций malloc/free  не возвращают освобожденную
        память операционной системе (если таковая имеется), а просто
        делают освобожденную память доступной для будущих вызовов malloc() в
        рамках того же процесса.

3.11    Должен ли я освобождать выделенную память перед возвратом в
        операцинную систему?

О:      Делать это не обязательно. Настоящая операционная система
        восстанавливает состояние памяти по окончании работы программы.
        Тем не менее, о некоторых персональных компьютерах известно, что они
ненадежны при восстановлении памяти, а из стандарта  ANSI/ISO
        можно лишь получить указание, что эти вопросы относятся к "качеству
        реализации".

        См. ANSI Разд. 4.10.3.2 .

3.12 Правильно ли использовать нулевой указатель в качестве первого
аргумента функции realloc()? Зачем это нужно?

О:      Это разрешено стандартом ANSI C (можно также использовать
        realloc(...,0) для освобождения памяти), но некоторые ранние
        реализации С это не поддерживают, и мобильность в этом случае не
        гарантируется.  Передача нулевого указателя realloc() может упростить
        написание самостартующего алгоритма пошагового выделения памяти.

        См.  ANSI Разд. 4.10.3.4 .

3.13:   В чем разница между calloc и malloc? Получатся ли в результате
        примения calloc корректные значения нулевых указателей и чисел с
        плавающей точкой? Освобождает ли free память, выделенную calloc,
или нужно использовать cfree?

О: По существу calloc(m,n) эквивалентна

p = malloc(m * n);
memset(p, 0, m * n);

Заполнение нулями означает зануление всех битов, и, следовательно,
        не гарантирует нулевых значений для указателей (см. раздел 1) и
        для чисел с плавающей точкой. Функция free может (и должна)
        использоваться для освобождения памяти, выделенной calloc.

         Смотри: ANSI Разделы от 4.10.3 до 4.10.3.2 .

3.14:   Что такое alloca и почему использование этой функции обескураживает?

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

        Использование  alloca не может  быть мобильным, реализации этой
функции трудны на машинах без стека. Использование этой функции
проблематично (и очевидная реализация на машинах со стеком не
удается), когда возвращаемое ей значение непосредственно передается
        другой функции, как, например, в fgets(alloca(100), 100, stdin).

        По изложенным  выше причинам  alloca (вне зависимости от того,
        насколько это может быть полезно) нельзя использовать в программах,
        которые должны быть в высокой степени мобильны.

        См. ANSI Rationale Разд. 4.10.3 c. 102.

4. Выражения

4.1: Почему вот такой код

a[i] = i++;

не работает?

О:      Подвыражение  i++  приводит к побочному эффекту -  значение  i
изменяется, что приводит к неопределенности, если i уже встречается в
том же выражении. (Обратите внимание на то, что хотя в книге K&R
        говорится, что поведение подобных выражений не описано, стандарт
        ANSI/ISO утверждает, что поведение не определено - см. вопрос 5.23.)

        См. ANSI Разд.3.3 c. 39.

4.2: Пропустив код

                int i = 7;
printf("%d\n", i++ * i++);

через свой компилятор, я  получил на выходе 49. А разве, независимо
от  порядка  вычислений, результат не должен быть равен 56?

О: Хотя  при  использовании постфиксной  формы  операторов ++  и --
        увеличение и уменьшение выполняется после того как первоначальное
значение использовано, тайный смысл слова "после" часто понимается
неверно. _Не_ гарантируется,  что увеличение или уменьшение будет
выполнено немедленно после использования первоначального значения
перед тем как будет вычислена любая другая часть выражения. Просто
гарантируется, что измение будет произведено в какой-то момент до
окончания вычисления (перед следующей "точкой последовательности"
в терминах ANSI C).  В приведенном примере компилятор умножил
предыдущее значение само на себя и затем дважды увеличил i на 1.

Поведение кода, содержащего  многочисленные двусмысленные побочные
        эффекты  неопределено  (см. вопрос 5.23). Даже не пытайтесь выяснить,
        как Ваш компилятор все это делает (в противоположность неумным
        упражнениям во многих книгах по С); в K&R мудро сказано: "Да
        хранит Вас Ваша невинность, если Вы не знаете, как это делается
        на разных машинах"

        См.: K&R I Разд. 2.12 c. 50; K&R II Разд. 2.12 c. 54; ANSI
        Разд. 3.3 c. 39; CT&P Разд. 3.7 c. 47; PCS Разд. 9.5 c. 120-1.
        (Не принимайте во внимание H&S Разд. 7.12 c. 190-1, это устарело)

4.3: Я экспериментировал с кодом

                int i = 2;
i = i++;

Некоторые компиляторы выдавали i=2, некоторые 3, но один выдал 4.
        Я знаю, что поведение неопределено, но как можно получить 4?

О:      Неопределенное (undefined) поведение означает, что может случиться
        _все_ что угодно. См. вопрос 5.23.

4.4     Люди твердят, что поведение неопределено, а я попробовал ANSI -
        компилятор и получил то, что ожидал.

О:      Компилятор делает все, что ему заблагорассудится, когда встречается
        с неопределенным поведением (до некоторой степени это относится и к
        случаю зависимого от реализации и неописанного поведения). В
        частности, он может делать то, что Вы ожидаете.  Неблагоразумно,
        однако, полагаться на это. См. также вопрос 5.18.

4.5: Могу я использовать круглые скобки, чтобы обеспечить нужный мне
порядок вычислений? Если нет, то разве приоритет операторов не
обеспечивает этого?

О:      Круглые скобки, как и приоритет  операторов обеспечивают лишь
        частичный порядок при вычислении выражений. Рассмотрим выражение

                 f() + g() * h()   .

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

4.6 Тогда как насчет операторов &&, //, и запятой ? Я имею в виду код
типа
if((c = getchar()) == EOF // c == '\n')" ...

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

        См.: K&R I Разд. 2.6 c. 38, Разд. A7.11-12 c. 190-1;
        K&R II Разд. 2.6 c. 41, Разд. A7.14-15 c. 207-8; ANSI
        Разд. 3.3.13 c. 52, 3.3.14 c. 52, 3.3.15 c. 53, 3.3.17 c. 55,
        CT&P Разд. 3.7 c. 46-7.

4.7 Если я не использую значение выражения, то как я должен увеличивать
        переменную i: так: ++i или так: i++ ?

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

4.8 Почему неправильно работает код

int a = 1000, b = 1000;
long int c = a * b;   ?

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

       long int c = (long int)a * b;

        Заметьте, что код (long int)(a * b) _не_ приведет к желаемому
        результату.

5.  ANSI C

5.1 Что  такое стандарт ANSI C ?

О: В 1983 году  Американский институт национальных стандартов (ANSI)
учредил комитет X3J11, чтобы разработать стандарт языка С. После
длительной и трудной работы, включающей выпуск нескольких публичных
отчетов, работа комитета завершилась 14 декабря 1989 г.созданием
        стандарта ANS X3.159-1989. Стандарт был опубликован весной 1990 г.
В большинстве случаев ANSI C узаконил уже существующую практику и
сделал несколько заимствований из С++ (наиболее важное - введение
прототипов функций). Была также добавлена поддержка национальных
        наборов символов (включая подвергшиеся наибольшим нападкам
        трехзнаковые последовательности). Стандарт ANSI C формализовал также
стандартную библиотеку.

        Опубликованный стандарт включает "Комментарии" ("Rationale"), в
        которых объясняются многие решения и обсуждаются многие тонкие
        вопросы, включая несколько затронутых здесь. ("Комментарии"
        не входят в стандарт ANSI X3.159-1989, они приводятся в качестве
        дополнительной информации.)

Стандарт ANSI был принят в качестве международного стандарта ISO/IEC
9899:1990,  хотя  нумерация разделов иная (разделы 2 - 4 стандарта
        ANSI соответствуют разделам 5 - 7 стандарта  ISO), раздел
        "Комментарии" не был включен.

5.2 Как получить копию Стандарта?

ANSI X3.159 был официально заменен стандартом ISO 9899. Копию
стандарта можно получить по адресу

American National Standards Institute
11 W. 42nd St., 13th floor
New York, NY 10036  USA
(+1) 212 642 4900
или
Global Engineering Documents
2805 McGaw Avenue
Irvine, CA  92714  USA
(+1) 714 261 1455
(800) 854 7179  (U.S. & Canada)

        В других странах свяжитесь с местным комитетом по стандартам или
        обратитесь в Национальный Комитет по Стандартам в Женеве

  ISO Sales
  Case Postale 56
  CH-1211 Geneve 20
  Switzerland

Цена составляет в ANSI $130, в Global Engineering Documents - $160.
        Копии оригинального стандарта Х3.159, включающие "Комментарии",
        попрежнему доступны за $205.00 (ANSI) или за $200.50 (Global
        Engineering Documents). Отметим, что комитет ANSI для поддержки своей
        деятельности получает доход от продажи отпечатанных копий стандарта,
        так что электронные копии _недоступны_.

Книга Герберта Шилдта с обманчивым названием "Комментарии к стандарту
        С" содержит лишь несколько страниц стандарта ISO 9899; опубликована
        издательством Osborne/McGraw-Hill, ISBN 0-07-881952-0 и продается
        примерно за $40. (Есть мнение, что различие в цене между официальным
        стандартом и комментарием Герберта Шилдта соответствует ценности
        комментария).

        Текст "Комментариев" (не всего стандарта) теперь доступен через
        ftp ftp.uu.net (см. вопрос 17.12) в директории doc/standards/ansi/
        X3.159-1989. "Комментарии" были также изданы издательством Silicon
        Press, ISBN 0-929306-07-4.

5.3 Есть ли у кого-нибудь утилиты для перевода С-программ, написанных в
        в старом стиле, в ANSI C и наоборот? Существуют ли программы для
        автоматического создания прототипов?

О:      Две программы, protoize и unprotoize осуществляют преобразование в
        обе стороны между функциями, записанными в новом стиле с прототипами,
        и функциями, записанными в старом стиле. (Эти программы не
        поддерживают полный перевод между "классическим" и ANSI C).
        Упомянутые программы были сначала вставками в FSF GNU компилятор С,
        gcc, но теперь они - часть дистрибутива gcc; смотри директорий
        pub/gnu на prep.ai.mit.edu (18.71.0.38), или в других архивах FSF.

Программа unproto ((/pub/unix/unproto5.shar.Z на ftp.win.tue.nl -
        это фильтр, располагающийся между препроцессором и следующим
        проходом компилятора - на лету переводит большинство особенностей
        ANSI C в традиционный С.

GNU пакет GhostScript содержит маленькую программу ansi2knr.

Есть несколько генераторов прототипов, многие из них - модификации
        программы lint. Версия 3 программы CPROTO была помещена в конференцию
        comp.sources.misc в марте 1992 г. Есть другая программа, которая
называется "ctxtract". См. вопрос 17.12.

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

5.4     Я пытаюсь использовать ANSI- строкообразующий оператор #, чтобы
        вставить в сообщение значение символической константы, но вставляется
        формальный параметр макроса, а не его значение.

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

#define str(x) #x
#define xstr(x) str(x)
#define OP plus
char *opname = xstr(OP);

Такая процедура устанавливает opname равным "plus", а не "OP".
Такие же обходные маневры необходимы при использовании оператора
        склеивания лексем ##, когда нужно соединить значения (а не имена
        формальных параметров) двух макросов.

        Смотри: ANSI Разд. 3.8.3.2, Разд. 3.8.3.5 пример c. 93.

Читать 3 часть


Страница сайта http://185.71.96.61
Оригинал находится по адресу http://185.71.96.61/home.asp?artId=21222