Стив Саммит. Язык си в вопросах и ответах. Структуры, перечисления и объединения.
Ответы на вопросы разбиты по темам:
4. Выражения
5. ANSI C
7. Списки аргументов переменной длины.
8. Булевы выражения и переменные.
9. Структуры, перечисления и объединения.
10. Декларации.
13. Lint.
14. Стиль.
15. Операции с плавающей точкой.
16. Интерфейс с операционной системой.
17. Разное (Пребразование Fortran -> C , грамматики для YACC и т.п.)
Структуры, перечисления и объединения.
9.1 Какова разница между enum и рядом директив препроцессора #define?
О: В настоящее время разница невелика. Хотя многие, возможно, предпочли бы иное решение, стандарт ANSI утверждает, что произвольному числу элементов перечисления могут быть явно присвоены целочисленные значения. (Запрет на присвоение значений без явного приведения типов, позволил бы при разумном использовании перечислений избежать некоторых ошибок.)
Некоторые преимущества перечислений в том, что конкретные значения задаются автоматически, что отладчик может представлять значения перечислимых переменных в символьном виде, а также в том, что перечислимые переменные подчиняются обычным правилам областей действия. (Компилятор может также выдавать предупредения, когда перечисления необдуманно смешиваются с целочисленными переменными. Такое смешение может рассматриваться как проявление плохого стиля, хотя формально это не запрещено). Недостаток перечислений в том, что у программиста мало возможностей управлять размером переменных (и предупреждениями компилятора тоже).
Смотри: K&R II Разд. 2.3 c. 39, Разд. A4.2 c. 196; H&S Разд. 5.5 c. 100; ANSI Разд. 3.1.2.5, 3.5.2, 3.5.2.2 .
9.2 Я слышал, что структуры можно копировать как целое, что они могут быть переданы функциям и возвращены ими, но в K&R I сказано, что этого делать нельзя.
О: В K&R I сказано лишь, что ограничения на операции со структурами будут сняты в следующих версиях компилятора; эти операции уже были возможны в компиляторе Денниса Ритчи, когда издавалась книга K&R I. Хотя некоторые старые компиляторы не поддерживают копирование структур, все современные компиляторы имеют такую возможность, предусмотренную стандартом ANSI C, так что не должно быть колебаний при копировании и передаче структур функциям.
Смотри: K&R I Разд. 6.2 c. 121; K&R II Разд. 6.2 c. 129; H&S Разд. 5.6.2 c. 103; ANSI Разд. 3.1.2.5, 3.2.2.1, 3.3.16 .
9.3 Каков механизм передачи и возврата структур?
О: Структура, передаваемая функции как параметр, обычно целиком размещается на стеке, используя необходимое количество машинных слов. (Часто для снижения ненужных затрат программисты предпочитают передавать функции указатель на структуру вместо самой структуры). Структуры часто возвращаются функциями в ту область памяти, на которую указывает дополнительный поддерживаемый компилятором «скрытый» аргумент. Некоторые старые компиляторы используют для возврата структур фиксированную область памяти, хотя это делает невозможным рекурсивный вызов такой функции, что противоречит стандарту ANSI.
Смотри: ANSI Разд.2.2.3 c. 13.
9.4 Эта программа работает правильно, но после завершения выдает дамп оперативной памяти. Почему?
struct list { char *item; struct list *next; }
/* Здесь функция main */
main(argc, argv) …
О: Из-за пропущенной точки с запятой компилятор считает, что main возвращает структуру. (Связь структуры с функцией main трудно определить, мешает комментарий). Так как для возврата структур компилятор обычно использует в качестве скрытого параметра указатель, код, сгенерированный для main() пытается принять три аргумента, хотя передаются (в данном случае стартовым кодом С) только два. См. также вопрос 17.21.
9.5 Почему нельзя сравнивать структуры?
О: Не существует разумного способа сделать сравнение структур совместимым с низкоуровневой природой языка С. Побайтовое сравнение может быть неверным из-за случайных бит в неиспользуемых «дырках» (такое заполнение необходимо, чтобы сохранить выравнивание для последующих полей; см. вопросы 9.10 и 9.11). Почленное сравнение потребовало бы неприемлевого количества повторяющихся машинных инструкций в случае больших структур.
Если необходимо сравнить две структуры, напишите для этого свою собственную функцию. C++ позволит создать оператор ==, чтобы связать его с Вашей функцией.
Смотри: K&R II Разд.6.2 c. 129; H&S Разд. 5.6.2 c. 103; ANSI Rationale разд. 3.3.9 c. 47.
9.7 Как читать/писать структуры из файла/в файл ?
О: Писать структуры в файл можно непосредственно с помощью fwrite:
fwrite((char *)&somestruct, sizeof(somestruct), 1, fp);
a cоответствующий вызов fread прочитает структуру из файла. Однако файлы, записанные таким образом будут _не_ особенно переносимы (см. вопросы 9.11 и 17.3). Заметьте также, что на многих системах нужно использовать в функции fopen флаг «b» .
9.7 Мне попалась программа, в которой структура определяется так:
struct name { int namelen; char name[1]; };
затем идут хитрые манипуляции с памятью, чтобы массив name вел себя будто в нем несколько элементов. Такие манипуляции законны/мобильны?
О: Такой прием популярен, хотя Деннис Ритчи назвал это «слишком фамильярным обращением с реализацией С». ANSI полагает, что выход за пределы объявленного размера члена структуры не полностью соответствует стандарту, хотя детальное обсуждение всех связанных с этим проблем не входит в задачу данных вопросов и ответов. Похоже, однако, что описанный прием будет одинаково хорошо принят всеми известными реализациями С. (Компиляторы, тщательно проверяющие границы массивов, могут выдать предупреждения). Для страховки будет лучше объявить переменную очень большого размера чем очень малого. В нашем случае
… char name[MAXSIZE]; …
где MAXSIZE больше, чем длина любого имени, которое будет сохранено в массиве name[]. (Есть мнение, что такая модификация будет соответствовать Стандарту).
Смотри: ANSI Rationale Разд. 3.5.4.2 c. 54-5.
9.8 Как определить смещение члена структуры в байтах?
О: Если это возможно, необходимо использовать макрос offsetof, который определен стандартом ANSI; см. <stddef.h>. Если макрос отсутствует, предлагается такая (не на все 100% мобильная) его реализация
#define offsetof(type, mem) ((size_t) \ ((char *)&((type *) 0)->mem — (char *)((type *) 0)))
Для некоторых компиляторов использование этого макроса может оказаться незаконным. О том, как использовать offsetof(), смотри следующий вопрос.
Смотри: ANSI Разд. 4.1.5, Rationale Разд. 3.5.4.2 c. 55.
9.9 Как осуществить доступ к членам структур по их именам во время выполнения программы?
О: Создайте таблицу имен и смещений, используя макрос offsetof(). Смещение члена структуры b в структуре типа a равно
offsetb = offsetof(struct a, b)
Если structp указывает на начало структуры, а b — член структуры типа int, смещение которого получено выше, b может быть установлен косвенно с помощью
*(int *)((char *)structp + offsetb) = value;
9.10 Почему sizeof выдает больший размер структурного типа, чем я ожидал, как будто в конце структры лишние символы?
О: Это происходит (возможны также внутренние «дыры» ; см. также вопрос 9.5), когда необходимо выравнивание при задании массива непрерывных структур.
9.11 Мой компилятор оставляет дыры в структурах, что приводит к потере памяти и препятствует «двоичному» вводу/выводу при работе с внешними файлами. Могу я отключить «дырообразование» или как-то контролировать выравнивание?
О: В Вашем компиляторе, возможно, есть расширение, (например, #pragma), которое позволит это сделать, но стандартного способа не существует. См. также вопрос 17.3.
9.12 Можно ли задавать начальные значения объединений?
О: Стандарт ANSI допускает инициализацию первого члена объединения. Не существует стандартного способа инициализации других членов. (и тем более нет такого способа для старых компиляторов, которые вообще не поддерживают какой-либо инициализации).
9.13 Как передать функциии структуру, у которой все члены — константы?
О: Поскольку в языке С нет возможности создавать безымянные значения структурного типа, необходимо создать временную структуру.