Почему мы должны так часто определять структуру в C?

270

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

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

elem user;

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

  • 13
    Более подробный и точный ответ: stackoverflow.com/questions/612328/…
  • 0
    У этого есть недостатки, я думаю, вы не можете создать список ссылок с анонимной структурой, потому что строка struct * ptr внутри структуры вызовет ошибку
Показать ещё 3 комментария
Теги:
struct
typedef

14 ответов

334
Лучший ответ

Как сказал Грег Хьюджилл, 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 +, например.

UPDATE Обратите внимание, что есть также высокоценные проекты C, где использование typedef для скрытия struct считается плохой идеей, ядро ​​Linux, вероятно, является наиболее известным проект. См. Главу 5 Документ ядра KodingStyle Linux для гневных слов Линуса.:) Я хочу сказать, что "вопрос" в вопросе, возможно, не установлен в камне, в конце концов.

  • 53
    Вы не должны использовать идентификаторы с подчеркиванием, за которым следует заглавная буква, они зарезервированы (см. Раздел 7.1.3, пункт 1). Хотя это вряд ли является большой проблемой, это технически неопределенное поведение при их использовании (7.1.3, параграф 2).
  • 0
    Как насчет плюсов и минусов этого синтаксиса: typedef struct Point {int x, y; } Укажите на эту ссылку: en.wikipedia.org/wiki/Struct_(C_programming_language)#typedef обсуждает проблему, но неоднозначно говорит, пользуется она или нет
Показать ещё 23 комментария
158

Удивительно, как многие люди ошибаются. ПОЖАЛУЙСТА, не создавайте структуры typedef на 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);

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

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

  • 32
    Что еще более удивительно, то, что через 13 месяцев после того, как этот ответ дан, я первый, кто проголосовал за него! Структуры typedef'ing - одно из самых больших злоупотреблений C, и они не имеют места в хорошо написанном коде. typedef полезен для де-запутывания запутанных типов указателей на функции и действительно не служит никакой другой полезной цели.
  • 27
    Питер ван дер Линден (Peter van der Linden) также приводит аргументы против структур определения типов в своей просвещающей книге «Программирование на языке C - секреты Deep C». Суть в том, что вы ХОТИТЕ знать, что что-то является структурой или объединением, а не скрывать это.
Показать ещё 10 комментариев
114

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


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

В C имя s, появляющееся в

struct s
    {
    ...
    };

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

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

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

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

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

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

struct s s;

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

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

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

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

S x;
S *p;

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

S S;    // error

Это хорошо.

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

typedef struct
    {
    ...
    } S;

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

  • 4
    Спасибо за четкое объяснение всей проблемы. Как парень C ++, я даже не знал об этих тегах.
  • 2
    Один из примеров того, где имя тега совпадает с именем без тега, приведен в программе (POSIX или Unix) с функцией int stat(const char *restrict path, struct stat *restrict buf) . Там у вас есть функция stat в обычном пространстве имен и struct stat в пространстве имен тегов.
Показать ещё 2 комментария
51

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

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct
  • 1
    хорошо, у нас нет этой проблемы в C ++. Так почему никто не удаляет этот глюк из компилятора C и делает его таким же, как в C ++. Хорошо, C ++ имеет несколько различных областей применения и поэтому имеет расширенные возможности. Но мы не можем наследовать некоторые из них в C без изменения оригинальный C?
  • 4
    Manoj, имя тега ("struct foo") необходимо, когда вам нужно определить структуру, которая ссылается на себя. например, «следующий» указатель в связанном списке. Более того, компилятор реализует стандарт, и это то, что говорит стандарт.
Показать ещё 2 комментария
36

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

enum EnumDef
{
  FIRST_ITEM,
  SECOND_ITEM
};

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

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

С typdef подобный тип опечаток привел бы к ошибкам компилятора для использования неизвестного типа:

typedef 
{
  FIRST_ITEM,
  SECOND_ITEM
} EnumDef;

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

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

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

  • 0
    Хуже того, ваша опечатка может совпадать с другим тегом. В случае структуры это может привести к правильной компиляции всей программы и неопределенному поведению во время выполнения.
  • 3
    это определение: 'typedef {FIRST_ITEM, SECOND_ITEM} EnumDef;' не определяет перечисление. Я написал сотни огромных программ, и мне не повезло в обслуживании программ, написанных другими. Исходя из сложного опыта, использование typedef в структуре приводит только к проблемам. Надеюсь, у программиста нет таких недостатков, что у них возникают проблемы при наборе полного определения при объявлении экземпляра структуры. C не является базовым, поэтому ввод еще нескольких символов не наносит ущерба работе программы.
Показать ещё 4 комментария
22

Стиль кодирования ядра 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 - отлично тонкие typedefs, хотя они лучше вписываются в категорию (d), чем здесь.

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

typedef unsigned long myflags_t;

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

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

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

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

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

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

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

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

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

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

  • 4
    «Непрозрачность и« функции доступа »сами по себе не годятся». Может кто-нибудь объяснить, почему? Я думаю, что сокрытие информации и инкапсуляция были бы очень хорошей идеей.
  • 5
    @ Явар Я только что прочитал этот документ и думал точно так же. Конечно, C не объектно-ориентирован, но абстракция все еще вещь.
9

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

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

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

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

  • 9
    Чистый не будет повторять struct везде ... Typedef делает новые типы. Что ты используешь? Типы. Нам не важно , структура это, объединение или перечисление, поэтому мы его определяем.
  • 10
    Нет, нам все равно, будет ли это структура или объединение, а не перечисление или какой-то атомарный тип. Вы не можете привести структуру к целому числу или к указателю (или к любому другому типу, если на то пошло), и это все, что вам иногда требуется для хранения некоторого контекста. Наличие ключевых слов «struct» или «union» вокруг улучшает локальность рассуждений. Никто не говорит, что вам нужно знать, что внутри структуры.
Показать ещё 7 комментариев
7

Оказывается, что есть пробки и минусы. Полезным источником информации является семантическая книга "Программирование экспертов 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;

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

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

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;

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

  • 1
    MyPipe *s; MyWriter *self = (MyWriter *) s; и вы только что нарушили строгий псевдоним.
  • 0
    @JonathonReinhart Было бы наглядно упомянуть, как этого можно избежать, например, как обходится безумно радующий GTK +: bugzilla.gnome.org/show_bug.cgi?id=140722 / mail.gnome.org/archives/gtk -devel-лист / 2004-апрель / msg00196.html
Показать ещё 1 комментарий
3

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

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


    struct MyStruct
    {
      int i;
    };

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

1

typedef не будет предоставлять ко-зависимый набор структур данных. Это невозможно сделать с typdef:

struct bar;
struct foo;

struct foo {
    struct bar *b;
};

struct bar {
    struct foo *f;
};

Конечно, вы всегда можете добавить:

typedef struct foo foo_t;
typedef struct bar bar_t;

В чем именно смысл?

0

A > typdef помогает в значении и документации программы, позволяя создавать более значимые синонимы для типов данных. Кроме того, они помогают параметризовать программу по проблемам переносимости (K & R, pg147, C prog lang).

В > структура определяет тип. Структуры позволяют удобно группировать коллекцию варов для удобства обработки (K & R, pg127, C prog lang.) Как единый блок

С > typedef'ing struct объясняется в выше.

D > Для меня структуры - это настраиваемые типы или контейнеры или коллекции, пространства имен или сложные типы, тогда как typdef - это просто средство для создания более псевдонимов.

0

Выключается в C99 typedef. Он устарел, но многие инструменты (ala HackRank) используют c99 в качестве своей чистой реализации C. И там typedef требуется.

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

  • 2
    «Оказывается, в C99 typedef требуется.» Что вы имеете в виду?
  • 0
    Вопрос о C , а не C++ . В C typedefs являются «обязательными» (и, скорее всего, всегда будут). «Обязательно», как и в случае, вы не сможете объявить переменную Point varName; и иметь тип быть синонимичным со struct Point; без typedef struct Point Point; ,
0

В языке программирования "C" ключевое слово "typedef" используется для объявления нового имени для какого-либо объекта (типа struct, array, function..enum). Например, я буду использовать "struct-s". В 'C' мы часто объявляем "структуру" вне "основной" функции. Например:

struct complex{ int real_part, img_part }COMPLEX;

main(){

 struct KOMPLEKS number; // number type is now a struct type
 number.real_part = 3;
 number.img_part = -1;
 printf("Number: %d.%d i \n",number.real_part, number.img_part);

}

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

typedef struct complex{int real_part, img_part; }COMPLEX;
//now COMPLEX is the new name for this structure and if I want to use it without
// a keyword like in the first example 'struct complex number'.

main(){

COMPLEX number; // number is now the same type as in the first example
number.real_part = 1;
number.img)part = 5;
printf("%d %d \n", number.real_part, number.img_part);

}

Если у вас есть локальный объект (struct, array, value), который будет использоваться во всей вашей программе, вы можете просто присвоить ему имя с помощью "typedef".

-2

На языке C, struct/union/enum - это макрокоманда, обрабатываемая препроцессором языка C (не ошибайтесь с препроцессором, который обрабатывает "#include" и другие)

так:

struct a
{
   int i;
};

struct b
{
   struct a;
   int i;
   int j;
};

struct b расходуется как-то вроде этого:

struct b
{
    struct a
    {
        int i;
    };
    int i;
    int j;
}

и так, во время компиляции он развивается в стеке как-то вроде: б: int ai int i int j

что также важно, чтобы иметь selfreferent structs, препроцессор C раунд в цикле деления, который не может завершиться.

typedef - это спецификатор типа, это означает, что только компилятор C обрабатывает его, и он может делать, как он хочет, для оптимизации реализации кода ассемблера. Он также не расходует член типа par, по-тупо, как préprocessor, с конструкциями, но использует более сложный алгоритм построения ссылок, поэтому построение вроде:

typedef struct a A; //anticipated declaration for member declaration

typedef struct a //Implemented declaration
{
    A* b; // member declaration
}A;

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

Это означает, что в C typedefs ближе к классу С++, чем к одиноким структурам.

  • 3
    Нет ничего сложного в том, чтобы иметь ссылочные структуры. struct foo {struct foo * next; инт вещь; }
  • 4
    ...какие? Сказать препроцессор, чтобы описать разрешение structs и typedef s, достаточно плохо, но остальная часть вашей записи настолько сбивает с толку, что мне трудно получить какое-либо сообщение от нее. Одна вещь , которую я могу сказать, однако, заключается в том , что ваша идея о том , что не- typedef d struct не может быть вперед объявлены или использоваться в качестве непрозрачного элемента (указатель) является абсолютно неверно. В вашем первом примере struct b может содержать тривиально struct a * , typedef требуется. Утверждения о том, что struct s являются просто тупыми частями макроразложения, и что typedef дают им новые революционные возможности, до боли неверны

Ещё вопросы

Сообщество Overcoder
Наверх
Меню