Расставим точки над структурами C/C++ / Habr
Недавно познакомился со структурами C/C++ — struct. Господи, да «что же с ними знакомиться» скажете вы? Тем самым вы допустите сразу 2 ошибки:Выравнивание полей в памяти
Обратите внимание на структуру:
struct Foo
{
char ch;
int value;
};
Ну во-первых какой у этой структуры размер в памяти?
sizeof(Foo)
?Размер этой структуры в памяти зависит от настроек компилятора и от директив в вашем коде…
В общем выравниваются в памяти поля по границе кратной своему же размеру. То есть 1-байтовые поля не выравниваются, 2-байтовые — выравниваются на чётные позиции, 4-байтовые — на позиции кратные четырём и т.д. В большинстве случаев (или просто предположим что сегодня это так) выравнивание размера структуры в памяти составляет 4 байта. Таким образом, sizeof(Foo) == 8
. Где и как прилепятся лишние 3 байта? Если вы не знаете — ни за что не угадаете…
- 1 байт: ch
- 2 байт: пусто
- 3 байт: пусто
- 4 байт: пусто
- 5 байт: value[0]
- 6 байт: value[1]
- 7 байт: value[2]
- 8 байт: value[3]
Посмотрим теперь размещение в памяти следующей структуры:
struct Foo
{
char ch;
short id;
int value;
};
Оно выглядит вот так:
- 1 байт: ch
- 2 байт: пусто
- 3 байт: id[0]
- 4 байт: id[1]
- 5 байт: value[0]
- 6 байт: value[1]
- 7 байт: value[2]
- 8 байт: value[3]
То есть, то что можно впихнуть до выравнивания по 4 байта — впихивается на ура (без увеличения размера структуры в памяти), добавим ещё одно поле:
struct Foo
{
char ch;
short id;
short opt;
int value;
};
Посмотрим на размещение полей в памяти:
- 1 байт: ch
- 2 байт: пусто
- 3 байт: id[0]
- 4 байт: id[1]
- 5 байт: opt[0]
- 6 байт: opt[1]
- 7 байт: пусто
- 8 байт: пусто
- 9 байт: value[0]
- 10 байт: value[1]
- 11 байт: value[2]
- 12 байт: value[3]
Всё это ой как печально, но есть способ бороться с этим прямо из кода:
#pragma pack(push, 1)
struct Foo
{
// ...
};
#pragma pack(pop)
Мы установили размер выравнивания в 1 байт, описали структуру и вернули предыдущую настройку. Возвращать предыдущую настройку — категорически рекомендую. Иначе всё может закончиться очень плачевно. У меня один раз такое было — падало Qt. Где-то заинклюдил их .h-ник ниже своего .h-ника…
Битовые поля
В комментариях мне указали на то, что битовые поля в структурах по стандарту являются «implementation defined» — потому их использования лучше избежать, но для меня соблазн слишком велик…
Мне становится не то что неспокойно на душе, а вообще становится хреново, когда я вижу в коде заполнение битовых полей при помощи масок и сдвигов, например так:
unsigned field = 0x00530000; // ... field &= 0xFFFF00FF; field |= (id) << 8; // ... field &= 0xFFFFFF83; field |= (proto) << 2;
Всё это пахнет такой печалью и такими ошибками и их отладкой, что у меня сразу же начинается мигрень! И тут из-за кулис выходят они — Битовые Поля. Что самое удивительное — были они ещё в языке C, но кого ни спрашиваю — все в первый раз о них слышат. Этот беспредел надо исправлять. Теперь буду давать им всем ссылку, ну или хотя бы ссылку на эту статью.
Как вам такой кусок кода:
#pragma pack(push,1)
struct IpHeader
{
uint8_t header_length:4;
uint8_t version:4;
uint8_t type_of_service;
uint16_t total_length;
uint16_t identificator;
// Flags
uint8_t _reserved:1;
uint8_t dont_fragment:1;
uint8_t more_fragments:1;
uint8_t fragment_offset_part1:5;
uint8_t fragment_offset_part2;
uint8_t time_to_live;
uint8_t protocol;
uint16_t checksum;
// ...
};
#pragma pack(pop)
А дальше в коде мы можем работать с полями как и всегда работаем с полями в C/C++. Всю работу по сдвигам и т.д. берет на себя компилятор. Конечно же есть некоторые ограничения… Когда вы перечисляете несколько битовых полей подряд, относящихся к одному физическому полю (я имею ввиду тип который стоит слева от имени битового поля) — указывайте имена для всех битов до конца поля, иначе доступа к этим битам у вас не будет,
#pragma pack(push,1)
stuct MyBitStruct
{
uint16_t a:4;
uint16_t b:4;
uint16_t c;
};
#pragma pack(pop)
Получилась структура на 4 байта! Две половины первого байта — это поля
a
и b
. Второй байт не доступен по имени и последние 2 байта доступны по имени c
. Это очень опасный момент. После того как описали структуру с битовыми полями обязательно проверьте её sizeof
!Также порядок размещения битовых болей в байте зависит от порядка байтов. При порядке LITTLE_ENDIAN битовые поля раздаются начиная со первых байтов, при BIG_ENDIAN — наоборот…
Порядок байтов
Меня также печалят в коде вызовы функций
htons()
, ntohs()
, htonl()
, nthol()
в коде на C++. На C это ещё допустимо, но не на С++. С этим я никогда не смирюсь! Внимание всё нижесказанное относится к C++!Ну тут я буду краток. Я в одной из своих предыдущих статей уже писал что нужно делать с порядками байтов. Есть возможность описать структуры, которые внешне работают как числа, а внутри сами определяют порядок хранения в байтах. Таким образом наша структура IP-заголовка будет выглядеть так:
#pragma pack(push,1)
struct IpHeader
{
uint8_t header_length:4;
uint8_t version:4;
uint8_t type_of_service;
u16be total_length;
u16be identificator;
// Flags
uint8_t _reserved:1;
uint8_t dont_fragment:1;
uint8_t more_fragments:1;
uint8_t fragment_offset_part1:5;
uint8_t fragment_offset_part2;
uint8_t time_to_live;
uint8_t protocol;
u16be checksum;
// ...
};
#pragma pack(pop)
Внимание собственно обращать на типы 2-байтовых полей —
u16be
. Теперь поля структуры не нуждаются ни в каких преобразованиях порядка байт. Остаются проблемы с fragment_offset
, ну а у кого их нет — проблем-то. Тем не менее тоже можно придумать шаблон, прячущий это безобразие, один раз его оттестировать и смело использовать во всём своём коде.«Язык С++ достаточно сложен, чтобы позволить нам писать на нём просто» © Как ни странно — Я
З.Ы. Планирую в одной из следующих статей выложить идеальные, с моей точки зрения, структуры для работы с заголовками протоколов стека TCP/IP. Отговорите — пока не поздно!
habr.com
Структуры в Си и их передача
Структура — это удобное хранилище для разнородных данных, которые хочется объединить. К примеру, вы можете создать структуру, описывающую параметры вашего устройства — сетевые настройки, таймаут спящего режима, его идентификатор и прочее подобное, типа какой-нибудь строки приветствия и состояния светодиода. Раз все параметры будут храниться в одном месте — они всегда будут на виду, да и нормальные IDE будут вам подсказывать поля структуры при обращении к ним. Ещё мы рассмотрим хранение и восстановление структур из архива, а также их передачу по сети.
Объявление такой структуры:
struct {
uint32_t ID;
char IP[4];
uint16_t timeout;
bool led;
char text[12];
} params;
Как это работает?
В си довольно удобный синтаксис, в том плане что многие вещи записываются как «тип_данных переменная», начиная с «int i» заканчивая «void main() {}». Так и здесь, кодовое слово struct начинает объявление структуры, и весь кусок кода «struct { … }» просто задаёт новый тип. Соответственно, params — это уже готовая переменная (экземпляр типа), которую можно использовать. Внутри фигурных скобок перечислены все поля структуры, которые потом будут доступны так: params.ID или params.IP[2]. Длина полей должна быть фиксированной, поэтому нельзя использовать строки вида *text, только массивы вида text[12].
Можно было сделать немного иначе: объявить только тип, а переменную завести позже. Для этого мы использовали бы ключевое слово typedef и написали так:
typedef struct {
uint32_t ID;
char IP[4];
uint16_t timeout;
bool led;
char text[12];
} params_struct;
params_struct params;
Так появляется возможность оставить все объявления структурных типов в отдельном файле (header), а в главном файле просто использовать уже готовые структурные типы для объявления структур прямо по месту.
Конечно, в обоих вариантах вы можете объявить сколько угодно экземпляров структур, или создать массив из них:
struct { uint32_t ID; char IP[4]; uint16_t timeout; bool led; char text[12]; } params1, params2, params[10];
Вариант с массивом особенно удобен для сервера в клиент-серверной топологии сети — на каждом клиенте хранятся в структуре его собственные параметры, а на мастер-устройстве располагается таблица параметров всех клиентов в виде массива структур.
В принципе, ничего сложного в структурах нет, а с темой серверов и клиентов мы плавно подошли к более интересной теме:
Хранение, передача и синхронизация структур
Для многих будет удивлением то, что данные структуры хранятся в памяти в виде плоского списка, все поля структуры просто идут в памяти друг за другом. Поэтому становится возможным обращаться с этой структурой как с простым массивом байт! Проверим, создадим массив «поверх» этой структуры.
Начальное смещение получим так:
char *Bytes = ¶ms;
мы объявили указатель char и поместили в него адрес params. Теперь Bytes указывает на первый байт структуры, и при последовательном чтении мы побайтно прочитаем всю структуру. Но сколько байт нужно прочитать? Для этого рассмотрим две интересных функции.
sizeof и offsetof
Это даже не функции, а встроенные макросы языка Си. Начнём с более простой, sizeof.
Компилятор заменяет все записи вида sizeof X на значение длины Х. В качестве X может выступать как тип, так и экзмепляр типа, т.е. в нашем случае можно подставить в sizeof и тип структуры (если мы его заводили с помощью typedef), и саму переменную структуры так: sizeof params_struct или sizeof params. Она пройдёт по всем полям структуры, сложит их длины и отдаст сумму, которая и будет длиной структуры.
offsetof — настоящий макрос, который принимает два параметра (структуру _s_ и поле _m_ в ней) и отдаёт положение этого поля в структуре, его смещение относительно начала структуры. Выглядит этот макрос очень просто:
offsetof(s, m) (size_t)&(((s *)0)-›m).
Как он работает?
- Берём число 0
- Преобразуем его к типу «указатель на структуру s»: (s*)0
- Обращаемся к полю m из этой структуры: ((s*)0)->m
- Вычисляем его адрес: &(((s*)0)->m)
- Преобразуем адрес к целому числу: (size_t)&(((s*)0)->m)
Магия именно в первом шаге, в котором мы берём 0. Благодаря этому на четвёртом шаге абсолютный адрес поля, вычисленный компилятором, оказывается отсчитан относительно начала структуры — структуру-то мы положили в адрес 0. Таким образом, после выполнения этого макроса мы реально имеем смещение поля относительно начала структуры. Понятно, что этот макрос правильно определит смещения даже в сложных и вложенных структурах.
Здесь нужно сделать небольшое отступление. Дело в том, что я рассматривал самый простой случай, когда поля упакованы точно вслед друг за другом. Есть и другие методы упаковки, которые называются «выравнивание». К примеру, можно выдавать каждому полю «слот», кратный 4 байтам, или 8 байтам. Тогда даже char будет занимать 8 байт, и общий размер структуры вырастет, а все смещения сдвинутся и станут кратны выравниванию. Эта штука полезна при программировании для компьютера, поскольку из-за грануляции ОЗУ процессор гораздо быстрее умеет извлекать из памяти выровненные данные, ему требуется на это меньше операций.
Работа с массивом из структуры
Окей, теперь мы умеем представлять любую структуру в виде массива байт, и обратно. Вы поняли фишку? У нас теперь одна и та же область памяти имеет роли «структура» и «массив». Изменяем что-то в структуре — меняется массив, меняем массив — меняется структура.
В этом — суть процесса! У нас нет отдельного массива, потому что сама структура — это уже массив, и мы просто обращаемся к памяти разными методами. И у нас нет никаких копирующих циклов по полям или по байтам, этот цикл будет уже сразу в функции передачи.
Теперь осталось лишь научиться удобно с этим всем работать.
Хранение и передача структуры
Чтобы создать архивную копию структуры, для передачи по сети или для складывания её в надёжное место — отдайте в вашу функцию передачи данных адрес этого массива. К примеру, моя функция записи массива данных в EEPROM выглядит так: I2C_burst_write (I2Cx, HW_address, addr, n_data, *data). Вам просто нужно вместо n_data передать sizeof params, а вместо *data — ¶ms:
I2C_burst_write (I2Cx, HW_address, addr, sizeof params, ¶ms)
Функции передачи данных по сети обычно выглядят примерно так же. В качестве данных передавайте ¶ms, а в качестве длины данных — sizeof params.
Приём и восстановление структуры
Всё точно так же. Моя функция чтения массива из EEPROM: I2C_burst_read (I2Cx, HW_address, addr, n_data, *data). n_data = sizeof params, *data = ¶ms:
I2C_burst_read (I2Cx, HW_address, addr, sizeof params, ¶ms)
Не забывайте, что вы сразу пишете принятые байты непосредственно в структуру. При медленной или ненадёжной передаче имеет смысл записать данные во временный буфер, и после их проверки передать их в структуру через
memcpy(¶ms, &temp_buffer, sizeof params).
Реализовав эти методы, мы воплотим удобную синхронизацию двух структур, находящихся на разных компьютерах: клиент-микроконтроллер может быть хоть на другой стороне земного шара от сервера, но передать структуры будет всё так же просто.
Хранение/восстановление отдельных полей
И зачем же мы так долго рассматривали макрос offsetof? Его очень удобно использовать для чтения и записи отдельных полей структуры, например так:
I2C_burst_write (I2Cx, HW_address, addr + offsetof(params, IP), sizeof params.IP, ¶ms.IP)
I2C_burst_read (I2Cx, HW_address, addr + offsetof(params, IP), sizeof params.IP, ¶ms.IP)
Ну и вообще, было бы неплохо сделать удобные макросы-обёртки для этой цели.
#define store(structure, field) I2C_burst_write (I2Cx, HW_address, addr + offsetof(structure, field), sizeof(structure.field), &(structure.field)) #define load(structure, field) I2C_burst_read (I2Cx, HW_address, addr + offsetof(structure, field), sizeof(structure.field), &(structure.field))
catethysis.ru
C определение структуры. Инициализация структуры в си (c struct)
Структура — это совокупность переменных, объединенных одним именем, предоставляющая общепринятый способ совместного хранения информации. Объявление структуры приводит к образованию шаблона, используемого для создания объектов структуры. Переменные, образующие структуру, называются членами структуры. (Члены структуры также часто называются элементами или полями.)
Обычно все члены структуры связаны друг с другом. Например, информация об имени и адресе, находящаяся в списке рассылки, обычно представляется в виде структуры. Следующий фрагмент кода объявляет шаблон структуры, определяющий имя и адрес. Ключевое слово struct сообщает компилятору об объявлении структуры.
Struct addr {
char name;
char street ; char city;
char state;
unsigned long int zip;
};
Объявление завершается точкой с запятой, поскольку объявление структуры — это оператор. Имя структуры addr идентифицирует структуру данных и является спецификатором типа. Имя структуры часто используют как ярлык.
На данный момент на самом деле не создано никакой переменной. Определена только форма данных. Для объявления настоящей переменной, соответствующей данной структуре, следует написать:
Struct addr addr_info;
В данной строке происходит объявление переменной addr_info типа addr. При объявлении структуры определяется переменная смешанного типа. До тех пор, пока не будет объявлена переменная данного типа, она не будет существовать.
Когда объявлена структурная переменная, компилятор автоматически выделяет необходимый участок памяти для размещения всех ее членов. Рис. показывает размещение addr_info в памяти.
При объявлении структуры можно одновременно объявить одну или несколько переменных.
Например:
Struct addr {
char name;
char street;
char city;
char state;
unsigned long int zip;
} addr_info; binfo, cinfo;
объявляет структуру addr и объявляет переменные addr_info, binfo, cinfo данного типа.
Важно понять, что каждая вновь создаваемая структурная переменная содержит свои собственный копии переменных, образующих структуру. Например, поле zip переменной binfo отделено от поля zip переменной cinfo. Фактически, единственная связь между binfo и cinfo заключается в том, что они обе являются экземплярами одного типа структуры. Больше между ними нет связи.
Если необходима только одна структурная переменная, то нет необходимости в ярлыке структуры. Это означает, что
Struct {
char name;
char street;
char city;
char state;
unsigned long int zip;
} addr_info;
Объявляет одну переменную addr_info с типом, определенным предшествующей ей структурой. Стандартный вид объявления структуры следующий:
struct ярлык {
тип имя переменной;
тип имя переменной;
тип имя переменной;
} структурные переменные;
Ярлык — это имя типа структуры, а не имя переменной. Структурные переменные — это разделенный запятыми список имен переменных. Следует помнить, что или ярлык, или структурные переменные могут отсутствовать, но не оба.
Структура в Си — тип данных, предназначенный для размещения значения разного типа в одном объекте. Полезен, когда необходимо объединить несколько переменных с разными типами под одним именем. Делают программу более компактной, ею удобней управлять. Структура имеет схожие особенности с массивами и классами.
Массивы
Прежде чем говорить о структуре в Си, нужно описать массив.
Существуют массивы одномерные, двумерные, трехмерные. Одномерный — это такой, у которого есть только одна строка с заполненными значениями. Двумерный — одномерный массив, внутри которого находятся другие одномерные массивы.
Обычный массив в Си записывается так: int a = {1, 2, 3, 4}.
Видим, что a — имя, int — тип данных, внутри фигурных скобок { } находятся значения, между квадратными скобками указывается длина, то есть количество элементов. Количество элементов является статическим, равняется 4. Это означает, что если в этом примере пользователь добавит пятое значение, компилятор выдаст ошибку. Если изначально не известно количество, они могут быть добавлены позже, но в квадратных скобках не ставится значение.
Двумерный объявляется похожим образом. Например, массив, который содержит 5 элементов-массивов, при этом каждый содержит по 3 элемента объявляется так: int a.По аналогии с одномерным добавлять ничего нельзя, чтобы не получить ошибку компилирования.
Различают динамические и статические. Статический — это такой, который вмещает фиксированное количество данных, то есть имеет постоянную длину. Под динамическим понимается тот, размер которого не ограничивается, он может меняться во время выполнения программы. Инициализация динамического массива происходит без указания точного количества.
Классы
Класс и структура похожи по между собой, но отличаются некоторыми нюансами. Что это такое? Это абстракция, описывающая методы еще не существующего объекта. После создания объект или, как он называется по-другому, экземпляр класса имеет конкретные свойства. Методы могут использоваться внутри, снаружи или при наследовании.
Класс объявляется так:
class /*class name*/
/* спецификатор доступа private обозначает, что управление методами возможно только внутри класса*/
/* делает свойства доступными для других частей кода */
/* наследуемые классы получают возможность использовать эти свойства */
Что такое структура в языке Си
Предназначена для хранения несколько типов данных. Например, чтобы создать каталог журналов, ну
offlink.ru
Программирование на C и C++
Структура — это совокупность переменных, объединенных одним именем, предоставляющая общепринятый способ совместного хранения информации. Объявление структуры приводит к образованию шаблона, используемого для создания объектов структуры. Переменные, образующие структуру, называются членами структуры. (Члены структуры также часто называются элементами или полями.)
Обычно все члены структуры связаны друг с другом. Например, информация об имени и адресе, находящаяся в списке рассылки, обычно представляется в виде структуры. Следующий фрагмент кода объявляет шаблон структуры, определяющий имя и адрес. Ключевое слово struct сообщает компилятору об объявлении структуры.
struct addr {
char name[30];
char street [40]; char city[20];
char state[3];
unsigned long int zip;
};
Объявление завершается точкой с запятой, поскольку объявление структуры — это оператор. Имя структуры addr идентифицирует структуру данных и является спецификатором типа. Имя структуры часто используют как ярлык.
На данный момент на самом деле не создано никакой переменной. Определена только форма данных. Для объявления настоящей переменной, соответствующей данной структуре, следует написать:
struct addr addr_info;
В данной строке происходит объявление переменной addr_info типа addr. При объявлении структуры определяется переменная смешанного типа. До тех пор, пока не будет объявлена переменная данного типа, она не будет существовать.
Когда объявлена структурная переменная, компилятор автоматически выделяет необходимый участок памяти для размещения всех ее членов. Рис. показывает размещение addr_info в памяти.
При объявлении структуры можно одновременно объявить одну или несколько переменных.
Например:
struct addr {
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
} addr_info; binfo, cinfo;
объявляет структуру addr и объявляет переменные addr_info, binfo, cinfo данного типа.
Важно понять, что каждая вновь создаваемая структурная переменная содержит свои собственный копии переменных, образующих структуру. Например, поле zip переменной binfo отделено от поля zip переменной cinfo. Фактически, единственная связь между binfo и cinfo заключается в том, что они обе являются экземплярами одного типа структуры. Больше между ними нет связи.
Если необходима только одна структурная переменная, то нет необходимости в ярлыке структуры. Это означает, что
struct {
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
} addr_info;
объявляет одну переменную addr_info с типом, определенным предшествующей ей структурой. Стандартный вид объявления структуры следующий:
struct ярлык {
тип имя переменной;
тип имя переменной;
тип имя переменной;
} структурные переменные;
ярлык — это имя типа структуры, а не имя переменной. Структурные переменные — это разделенный запятыми список имен переменных. Следует помнить, что или ярлык, или структурные переменные могут отсутствовать, но не оба.
www.c-cpp.ru
Структуры данных в языке Си — Анализ данных в социально-экономических системах
Содержание пособия и его концепция.
В данной главе рассказывается о том, как из простых структур данных конструируются более сложные.
Известно, что ПК оперирует физическими структурами данных, в отличие от программиста, который для решения задач пользуется логическими структурами. Далее речь пойдет о логических структурах.
Структуры данных связаны с типами данных.
Тип данных – набор значений, хранящихся в переменных, а также набор операций, выполнение которых допускается над этими значениями.
1) Базовые типы в языке С:
char – единичный байт, который может содержать один символ.
int – целое.
float – число с плавающей точкой одинарной точности.
double – число с плавающей точкой двойной точности.
Примеры.
Язык С не является сильно типизированным языком – допускаются почти все преобразования типов.
[c]
int m;
m = 1.6 + 1.7;
m = (int) 1.6 + (int) 1.7;
[/c]
2) Массив – набор переменных одного типа, имеющих одно и тоже имя.
кол-во байтов памяти массива = sizeof(базовый тип) * длина_массива
Многомерные массивы.
Массивы базовых типов. Пример.
— Массив символьных переменных.
Строка – массив символьных переменных, заканчивающийся спец. нулевым символом ‘\0’, поэтому необходимо выделить на 1 символ больше.
Пример.
3) Указатели.
[c]
int x=1, y=2, z[100];
int *p; // указатель на int, служит для хранения адреса
p=&x; // p указывает на x, т.е. хранит адрес x
y = *p; // берем содержимое p по адресу
p = z; // хранит адрес нулевого элемента
p=&z[0]; // получение адреса нулевого эл-та
[/c]
Массив указателей. Пример.
4) Структуры.
Структура – совокупность переменных, объединенных под одним именем.
Объявление структуры создает шаблон, который можно использовать для создания ее объектов (экземпляров структуры).
Переменные, из которых состоит структура, называются элементами/полями.
Поля структуры связаны друг с другом по смыслу.
[c]
struct addr {
char name[30];
char street[40];
char city[20];
int zip;
};
struct addr addr_info; // addr_info является экземпляром (объектом) структуры addr
[/c]
Пример учебный.
Пример из реальной системы.
Задачка.
Массив структур
struct addr addr_list[100];
Указатели на структуры
struct addr * addr_pointer;
5) Списковая структура — набор элементов, каждый из которых состоит из двух полей: в одном – элемент данных или указатель на него, в другом – указатель на следующий элемент списка.
Односвязный список
[c]
struct addr {
char name[30];
char street[40];
char city[20];
int zip;
struct addr *next; // ссылка на след. адрес
} info;
[/c]
Пример.
pycode.ru
typedef struct vs struct definitions
Я вижу, что в этом есть некоторые разъяснения. C и C ++ не определяют типы по-разному. C ++ первоначально был не чем иным, как дополнительным набором включений поверх C.
Проблема, с которой сегодня сталкиваются практически все разработчики C / C ++: а) университеты больше не учат основам, а б) люди не понимают разницы между определением и декларацией.
Единственная причина, по которой такие декларации и определения существуют, заключается в том, что компоновщик может вычислять смещения адресов в полях структуры. Вот почему большинство людей уходит с кодом, который на самом деле написан неправильно — потому что компилятор способен определить адресацию. Проблема возникает, когда кто-то пытается сделать что-то заранее, например, в очереди или связанном списке, или копируя структуру O / S.
Объявление начинается с «struct», определение начинается с «typedef».
Кроме того, структура имеет ярлык декларации вперед и определенную метку. Большинство людей этого не знают и используют ярлык прямого объявления как метку define.
Неправильно:
struct myStruct
{
int field_1;
...
};
Они просто использовали декларацию forward для обозначения структуры — так что теперь компилятор знает об этом, но это не определенный тип. Компилятор может рассчитать адресацию — но это не то, как оно предназначалось для использования, по причинам, которые я покажу на мгновение.
Люди, которые используют эту форму декларации, всегда должны всегда «структурировать» каждую ссылку на нее, потому что это не официальный новый тип.
Вместо этого любая структура, которая не ссылается сама, должна быть объявлена и определена только так:
typedef struct
{
field_1;
...
}myStruct;
Теперь это фактический тип, и при использовании вы можете использовать его как «myStruct», не добавляя его со словом «struct».
Если вам нужн
code.i-harness.com