Зачем этой структуре нужно значение size?

Я читал "начало OpenGL Game Programming Second Edition" и наткнулся на это определение структуры:

typedef struct tagPIXELFORMATDESCRIPTOR 
{
    WORD  nSize;    // size of the structure
    WORD  nVersion; // always set to 1
    DWORD dwFlags;  // flags for pixel buffer properties
    ...
}

" первым из наиболее важных полей в структуре является nSize. Этот поле всегда должно быть установлено равным размеру структуры, например это: ОФП.nSize = sizeof (PIXELFORMATDESCRIPTOR); это простой и является общим требованием для структур данных, которые передаются как указатели. Часто, структура должна знать свое размер и сколько для него выделена память при выполнении различных операций. Размер field позволяет легко и точно получить доступ к этой информации." (ПГ. 24)

почему структура необходимость пользователя пройти размера? Может ли код, который использует эту структуру, не просто использовать sizeof () при необходимости?

6 ответов


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

  1. точное определение структуры будет меняться со временем, поскольку API библиотеки, который использует его, развивается. Новые поля будут добавлены в конце, изменив определение структуры и изменив ее sizeof. Тем не менее, устаревший код по-прежнему будет поставлять "старую" меньшую структуру тем же функциям API. Чтобы убедиться, что старый и новый код работают,времени информация о размере необходима. Формально это то, что nVersion поле можно использовать для. Это поле само по себе должно быть достаточным, чтобы сообщить API, какую версию API ожидает использовать вызывающий код и сколько полей он выделяет в структуре. Но только для дополнительной безопасности информация о размере может быть предоставлена через независимый nSize поле, что неплохая идея.

  2. структура содержит необязательную или гибкую информацию (независимо от версии API). Заполняя код либо решите, какая информация вам нужна или не нужна на основе этого размера, либо усеките информацию гибкого размера на основе запрошенного размера. Это может быть особенно уместно, если структура имеет гибкий член массива в конце (вдоль строк "ударил Хак" или такой).

в данном конкретном случае (PIXELFORMATDESCRIPTOR struct из Windows API) это первая причина, которая применяется, поскольку в этой структуре и связанном API нет ничего гибкого.


Это позволяет определению структуры изменяться с течением времени. По мере добавления новых полей в конце поле размер указывает, какую версию использовать.


может ли код, который использует эту структуру, не просто использовать sizeof () при необходимости?

это идея-не используйте sizeof чтобы определить размер сообщения. Такая структура очень распространена в серверном программировании при использовании связи через сокеты, а также распространена в WinAPI.

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

считайте, если вы получаете заполненный буфер данных и есть три PIXELFORMATDESCRIPTOR сообщения в ней. Если вы знаете размер каждого сообщения Вы можете правильно переходить от одного сообщения к далее при обработке буфера. Откуда вы знаете размер каждого сообщения?

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

проверка sizeof определить размер сообщения на проводе не является устойчивым подход. Лучше, чтобы протокол сообщал вам в режиме реального времени, насколько велико каждое сообщение, и обрабатывал столько байтов из буфера при разборе сообщения. Если размер сообщения находится в одном и том же месте для каждого типа сообщения (что является рекомендуемой практикой при разработке таких протоколов), вы даже можете правильно извлекать сообщения из буфера, о котором вы ничего не знаете.

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


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

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


представьте, что вы разработчик, создающий Windows API. У вас есть определенный набор вызовов API, документированных и выпущенных ОС. Многие из ваших текущих вызовов API принимают указатели на структуры в качестве входных аргументов, чтобы позволить передавать много входных значений без большого количества входных аргументов.

сейчас разработчики начинают писать код для вашей ОС.

несколько лет спустя вы решили создать новую версию ОС Windows. У вас есть некоторые требования, хотя:

  1. программы скомпилированный для предыдущей версии ОС должен по-прежнему выполняться на новой ОС - (Для этого API должен быть обратно совместим).
  2. вы хотите расширить свой API - (добавлены новые вызовы API).
  3. вы хотите разрешить разработчикам использовать свой существующий код (который они написали для старых окон) и разрешить им компилировать и выполнять его на новой ОС.

OK - для работы ваших старых программ ваш новый API должен иметь те же процедуры с теми же args и т. д.

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

обычно процедуры API требуют много информации, но неудобно создавать процедуры, которые имеют много формальных аргументов. Вот почему часто один из формальных args является апоинтером структуры, содержащей свойства, которые вы хотите передать в свою рутину. Это упрощает расширение API. Для пример:

ваш старый код:

struct abc
{
   int magicMember; // ;-) 
   int a;
   int b;
   int c;
};

void someApiCall( struct abc *p, int blaBla );

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

новый код:

// on new OS - defined in a header with the same name as older OS
// hence no includes changes 

struct abc
{
   int magicMember; // ;-) 
   int a;
   int b;
   int c;
   int new_stuff_a;
   int new_stuff_b;
};

void someApiCall( struct abc *p, int blaBla );

вы сохранили подпись подпрограммы и в то же время позволили работать как старому, так и новому коду. Единственный секрет magicMember который вы можете рассматривать как номер редакции struct или-если в новых версиях вы просто добавляете новые члены-размер структуры. В обоих случаях ваш "someApiCall" сможет различать 2 типа "одной и той же" структуры, и вы сможете выполнить этот вызов API как из старого, так и из нового кода.

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

для реального примера проверьте вызов API RegisterClassEx и структура WNDCLASSEX принимает


в большинстве случаев, если доступ к tagPIXELFORMATDESCRIPTOR использование указателей типа tagPIXELFORMATDESCRIPTOR*, вам не нужен член, чтобы указать размер; sizeof всегда будет давать вам правильный размер:

void func(tagPIXELFORMATDESCRIPTOR *ptr) {
    // sizeof *ptr is the correct size
}

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

например, вы можете определите тип, который содержит только элемент size:

struct empty {
    WORD  nSize;
};

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

void func(empty *ptr) {
    // sizeof *ptr is incorrect
    // ptr->nSize is the actual size (if you've done everything right)
    // That's ok if we don't need any information other than the size
}

...

tagPIXELFORMATDESCRIPTOR obj;
...
func(reinterpret_cast<empty*>(ptr));

это не значит, что это хорошая идея.

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

если вы не можете, C++ предоставляет много более чистые и надежные способы (особенно наследование) для определения связанных типов. Гораздо лучше определить, что я назвал empty (или, возможно, его следует назвать чем-то вроде descriptor) как класс, а затем определить tagPIXELFORMATDESCRIPTOR как подкласс.

Я не знаком с OpenGL, но я подозреваю, что он изначально был разработан для использования псевдо-наследования C-стиля. Вы можете обязательно придерживаться этой модели, Если вам нужно работать с объектами OpenGL в C или C++ код.