Почему мы должны так часто определять структуру в C? - PullRequest
360 голосов
/ 31 октября 2008

Я видел много программ, состоящих из структур, подобных приведенной ниже

typedef struct 
{
    int i;
    char k;
} elem;

elem user;

Зачем это нужно так часто? Любая конкретная причина или область применения?

Ответы [ 15 ]

423 голосов
/ 31 октября 2008

Как сказал Грег Хьюгилл, typedef означает, что вам больше не нужно писать struct повсюду. Это не только экономит нажатия клавиш, но и может сделать код чище, так как он обеспечивает smidgen больше абстракции.

Вещи как

typedef struct {
  int x, y;
} Point;

Point point_new(int x, int y)
{
  Point a;
  a.x = x;
  a.y = y;
  return a;
}

становится чище, когда вам не нужно видеть ключевое слово "struct" повсюду, это выглядит так, как будто на вашем языке действительно есть тип "Point". Который, после typedef, является случаем, я думаю.

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

typedef struct Point Point;

Point * point_new(int x, int y);

и затем укажите определение struct в файле реализации:

struct Point
{
  int x, y;
};

Point * point_new(int x, int y)
{
  Point *p;
  if((p = malloc(sizeof *p)) != NULL)
  {
    p->x = x;
    p->y = y;
  }
  return p;
}

В этом последнем случае вы не можете вернуть Point по значению, поскольку его определение скрыто от пользователей заголовочного файла. Этот метод широко используется, например, в GTK + .

ОБНОВЛЕНИЕ Обратите внимание, что существуют также высоко ценимые C-проекты, где использование typedef для сокрытия struct считается плохой идеей, ядро ​​Linux, вероятно, является наиболее известным таким проектом , См. Главу 5 документ Linux Kernel CodingStyle для гневных слов Линуса. :) Моя точка зрения такова, что слово «должен» в этом вопросе, возможно, вовсе не в камне.

198 голосов
/ 31 декабря 2010

Удивительно, сколько людей ошибаются. ПОЖАЛУЙСТА, не печатайте структуры типов в C, это излишне загрязняет глобальное пространство имен, которое обычно сильно загрязняется уже в больших программах на C.

Кроме того, структуры typedef'd без имени тега являются основной причиной ненужного наложения отношений упорядочения между заголовочными файлами.

Рассмотрим:

#ifndef FOO_H
#define FOO_H 1

#define FOO_DEF (0xDEADBABE)

struct bar; /* forward declaration, defined in bar.h*/

struct foo {
  struct bar *bar;
};

#endif

При таком определении, не используя typedefs, модуль compiland может включить foo.h, чтобы получить определение FOO_DEF. Если он не пытается разыменовать член 'bar' в структуре foo, тогда нет необходимости включать файл "bar.h".

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

struct foo *foo;

printf("foo->bar = %p", foo->bar);

Поскольку пространства имен разделены, нет конфликта именования переменных, совпадающих с их именем тега struct.

Если мне придется поддерживать ваш код, я удалю ваши структуры typedef'd.

135 голосов
/ 31 октября 2008

Из старой статьи Дэна Сакса (http://www.ddj.com/cpp/184403396?pgno=3):


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

В C имена появляются в

struct s
    {
    ...
    };

это тег. Имя тега не является типом название. Учитывая определение выше, объявления, такие как

s x;    /* error in C */
s *p;   /* error in C */

ошибки в C. Вы должны написать их а

struct s x;     /* OK */
struct s *p;    /* OK */

Названия союзов и перечислений также являются тегами, а не типами.

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

struct s s;

является действительной декларацией, которая объявляет переменная s типа struct s. Это может не быть хорошей практикой, но компиляторы C должен принять это. Я никогда не видел обоснование того, почему С был разработан этот путь. Я всегда думал, что это было ошибка, но это так.

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

struct s
    {
    ...
    };
typedef struct s S;

позволяет использовать S вместо struct s, как в

S x;
S *p;

Программа не может использовать S в качестве имени и тип и переменная (или функция или константа перечисления):

S S;    // error

Это хорошо.

Имя тега в структуре, объединении или Определение enum не является обязательным. Много программисты сворачивают определение структуры в typedef и обойтись без пометить в целом, как в:

typedef struct
    {
    ...
    } S;

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

58 голосов
/ 31 октября 2008

Использование typedef избавляет от необходимости писать struct каждый раз, когда вы объявляете переменную этого типа:

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct
38 голосов
/ 31 марта 2009

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

enum EnumDef
{
  FIRST_ITEM,
  SECOND_ITEM
};

struct StructDef
{
  enum EnuumDef MyEnum;
  unsigned int MyVar;
} MyStruct;

Обратите внимание на опечатку в EnumDef в структуре (Enu u mDef)? Он компилируется без ошибок (или предупреждений) и является (в зависимости от буквальной интерпретации стандарта C) правильным. Проблема в том, что я только что создал новое (пустое) определение перечисления в моей структуре. Я не использую (как предполагалось) предыдущее определение EnumDef.

При использовании typedef аналогичные опечатки привели бы к ошибкам компилятора при использовании неизвестного типа:

typedef 
{
  FIRST_ITEM,
  SECOND_ITEM
} EnumDef;

typedef struct
{
  EnuumDef MyEnum; /* compiler error (unknown type) */
  unsigned int MyVar;
} StructDef;
StrructDef MyStruct; /* compiler error (unknown type) */

Я бы выступал ВСЕГДА за определение структур и перечислений.

Не только для сохранения некоторого набора текста (без каламбура;)), но и потому, что это безопаснее.

29 голосов
/ 07 августа 2013

Стиль кодирования ядра Linux Глава 5 дает большие плюсы и минусы (в основном минусы) использования typedef.

Пожалуйста, не используйте такие вещи, как "vps_t".

Это ошибка использовать typedef для структур и указателей. Когда вы видите

vps_t a;

в источнике, что это значит?

Напротив, если он говорит

struct virtual_container *a;

Вы действительно можете сказать, что такое "а".

Многие думают, что typedefs "помогают читабельности". Не так. Они полезны только для:

(a) полностью непрозрачные объекты (где typedef активно используется, чтобы скрыть , что это за объект).

Пример: "pte_t" и т. Д. Непрозрачные объекты, доступ к которым можно получить только с помощью соответствующих функций доступа.

ВНИМАНИЕ! Непрозрачность и «функции доступа» сами по себе не годятся. Причина, по которой мы их используем для таких вещей, как pte_t и т. Д., Заключается в том, что на самом деле там абсолютно ноль портативно доступной информации.

(b) Очистить целочисленные типы, где абстракция помогает избежать путаницы, будь то "int" или "long".

u8 / u16 / u32 - отличные определения типов, хотя они лучше подходят для категории (d), чем здесь.

ВНИМАНИЕ! Опять же - для этого должна быть причина . Если что-то "unsigned long", то нет никаких причин делать

typedef unsigned long myflags_t;

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

(c) когда вы используете sparse для буквального создания нового типа для проверки типов.

(d) Новые типы, идентичные стандартным типам C99, при определенных исключительных обстоятельствах.

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

Поэтому разрешены специфичные для Linux типы 'u8 / u16 / u32 / u64' и их подписанные эквиваленты, которые идентичны стандартным типам, хотя они не являются обязательными в новом собственном коде.

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

(e) Типы, безопасные для использования в пользовательском пространстве.

В некоторых структурах, видимых для пользовательского пространства, мы не можем требовать типы C99 и не можем использовать форму «u32» выше. Таким образом, мы используем __u32 и подобные типы во всех структурах, которые используются совместно с пользовательским пространством.

Может быть, есть и другие случаи, но правило должно заключаться в том, чтобы НИКОГДА не использовать typedef, если вы не можете четко соответствовать одному из этих правил.

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

10 голосов
/ 11 августа 2013

Оказывается, есть плюсы и минусы. Полезным источником информации является оригинальная книга «Программирование на Expert C» ( Глава 3 ). Вкратце, в C у вас есть несколько пространств имен: теги, типы, имена членов и идентификаторы . typedef вводит псевдоним для типа и находит его в пространстве имен тега. А именно,

typedef struct Tag{
...members...
}Type;

определяет две вещи. Один тег в пространстве имен тегов и один тип в пространстве имен типов. Таким образом, вы можете сделать как Type myType, так и struct Tag myTagType. Объявления типа struct Type myType или Tag myTagType являются незаконными. Кроме того, в таком объявлении:

typedef Type *Type_ptr;

мы определяем указатель на наш тип. Итак, если мы объявим:

Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;

затем var1, var2 и myTagType1 являются указателями на Type, но myTagType2 нет.

В вышеупомянутой книге упоминается, что структуры определения типов не очень полезны, поскольку они только спасают программиста от написания слова struct. Однако у меня есть возражение, как и у многих других программистов на Си. Хотя иногда оказывается необходимым запутывать некоторые имена (поэтому это не рекомендуется в больших базах кода, таких как ядро), когда вы хотите реализовать полиморфизм в C, это очень помогает , ищите подробности Пример: * 1 027 *

typedef struct MyWriter_t{
    MyPipe super;
    MyQueue relative;
    uint32_t flags;
...
}MyWriter;

вы можете сделать:

void my_writer_func(MyPipe *s)
{
    MyWriter *self = (MyWriter *) s;
    uint32_t myFlags = self->flags;
...
}

Таким образом, вы можете получить доступ к внешнему члену (flags) по внутренней структуре (MyPipe) посредством приведения. Для меня это менее запутанно приводить весь тип, чем делать (struct MyWriter_ *) s; каждый раз, когда вы хотите выполнить такую ​​функцию. В этих случаях краткие ссылки имеют большое значение, особенно если вы интенсивно используете технику в своем коде.

Наконец, последний аспект с typedef типами ed - это невозможность их расширения, в отличие от макросов. Если, например, у вас есть:

#define X char[10] or
typedef char Y[10]

затем вы можете объявить

unsigned X x; but not
unsigned Y y;

Мы на самом деле не заботимся об этом для структур, потому что это не относится к спецификаторам хранилища (volatile и const).

10 голосов
/ 29 мая 2010

Я не думаю, что предварительные объявления возможны даже с typedef. Использование struct, enum и union позволяет пересылать объявления, когда зависимости (знают о них) являются двунаправленными.

Стиль: Использование typedef в C ++ имеет смысл. Это может почти понадобиться при работе с шаблонами, которые требуют нескольких и / или переменных параметров. Typedef помогает сохранить правильное наименование.

Не так на языке программирования Си. Использование typedef чаще всего служит не для того, чтобы запутать использование структуры данных. Поскольку только {struct (6), enum (4), union (5)} количество нажатий клавиш используются для объявления типа данных, практически нет смысла использовать псевдонимы структуры. Это тип данных объединение или структура? Использование простого не типизированного объявления позволяет сразу узнать, какой это тип.

Обратите внимание, как Linux написан со строгим избеганием этого псевдонима. Результат - минималистский и чистый стиль.

4 голосов
/ 09 марта 2018

Давайте начнем с основ и продолжим наш путь.

Вот пример определения структуры:

struct point
  {
    int x, y;
  };

Здесь имя point необязательно.

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

Объявление во время определения

struct point
  {
    int x, y;
  } first_point, second_point;

Объявление после определения

struct point
  {
    int x, y;
  };
struct point first_point, second_point;

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

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

typedef struct point
  {
    int x, y;
  } Points;

Points first_point, second_point;

Слово предостережения при названии вашего пользовательского типа

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

3 голосов
/ 31 октября 2008

имя, которое вы (необязательно) даете структуре, называется именем тега и, как было отмечено, не является типом само по себе. Для перехода к типу требуется префикс struct.

Помимо GTK +, я не уверен, что тэг используется так же часто, как typedef для типа struct, поэтому в C ++ это распознается, и вы можете опустить ключевое слово struct и использовать тэг в качестве имени типа:


    struct MyStruct
    {
      int i;
    };

    // The following is legal in C++:
    MyStruct obj;
    obj.i = 7;

...