#define BUFFER_OFFSET(i) ((char *)NULL + (i))
Технически результат этой операции равен undefined , а макрос фактически неверен. Позвольте мне объяснить:
C определяет (и C ++ следует за ним), что указатели можно приводить к целым числам, а именно к типу uintptr_t
, и что если целое число, полученное таким образом, приведенное к исходному типу указателя, из которого оно получено, даст оригинальный указатель.
Тогда есть арифметика указателей, что означает, что если у меня есть два указателя, указывающие на один и тот же объект, я могу взять их разность, в результате чего получается целое число (типа ptrdiff_t
), и это целое число прибавляется или вычитается к любому из оригинальные указатели, уступят другим. Также определяется, что, добавляя 1 к указателю, можно получить указатель на следующий элемент индексированного объекта. Также разность двух uintptr_t
, разделенных на sizeof(type pointed to)
указателей одного и того же объекта, должна быть равна вычитаемым указателям. И последнее, но не менее важное: значения uintptr_t
могут быть любыми. Они также могут быть непрозрачными ручками. Они не обязательно должны быть адресами (хотя большинство реализаций делают это таким образом, потому что это имеет смысл).
Теперь мы можем посмотреть на печально известный нулевой указатель. C определяет указатель, который приведен для типа uintptr_u
значение 0, как недопустимый указатель. Обратите внимание, что это всегда 0 в вашем исходном коде. Что касается серверной части, в скомпилированной программе двоичное значение, используемое для фактического ее представления на машине, может быть совершенно другим! Обычно это не так, но это может быть. C ++ - то же самое, но C ++ не допускает столь же неявного приведения, чем C, поэтому необходимо явно привести 0 к void*
. Кроме того, поскольку нулевой указатель не ссылается на объект и, следовательно, не имеет разыменованного размера, арифметика указателя не определена для нулевого указателя . Пустой указатель, не ссылающийся на объект, также означает, что нет никакого определения для разумного приведения его к типизированному указателю.
Итак, если это все не определено, почему этот макрос работает в конце концов? Потому что большинство реализаций (имеется в виду компиляторы) чрезвычайно доверчивы, а кодировщики компиляторов в высшей степени ленивы. Целочисленное значение указателя в большинстве реализаций - это просто значение самого указателя на стороне сервера. Таким образом, нулевой указатель на самом деле равен 0. И хотя арифметика указателя на нулевой указатель не проверяется, большинство компиляторов будут молча принимать его, если указатель получил какой-либо назначенный тип, даже если это не имеет смысла. char
- это тип C "единичного размера", если вы хотите так сказать. Таким образом, арифметика указателей при приведении аналогична арифметике по адресам на внутренней стороне.
Короче говоря, просто не имеет смысла пытаться делать магию указателей с ожидаемым результатом как смещение на стороне языка C. Это просто не работает таким образом.
Давайте ненадолго отступим и вспомним, что мы на самом деле пытаемся сделать: исходная проблема заключалась в том, что функции gl…Pointer
принимают указатель в качестве параметра данных, но для объектов буфера вершин мы фактически хотим указать байтовое смещение в наших данных, которое является числом. Компилятору C функция берет указатель (непрозрачная вещь, как мы узнали). Правильным решением было бы введение новых функций, особенно для использования с VBO (скажем, gl…Offset
- я думаю, что я собираюсь на Rally для их введения). Вместо этого то, что было определено OpenGL, является эксплойтом того, как работают компиляторы. Указатели и их целочисленный эквивалент реализованы в одном и том же двоичном представлении большинством компиляторов. Итак, что мы должны сделать, это заставить компилятор вызывать эти gl…Pointer
функции с нашим номером вместо указателя.
Технически, единственное, что нам нужно сделать, это сказать компилятору: «Да, я знаю, вы думаете, что эта переменная a
является целым числом, и вы правы, и эта функция glVertexPointer
принимает только void*
параметр данных. Но угадайте, что: это целое число было получено из void*
", приведя его к (void*)
и затем удерживая большие пальцы, что компилятор на самом деле настолько глуп, чтобы передавать целочисленное значение, как и glVertexPointer
.
Итак, все сводится к тому, чтобы как-то обойти сигнатуру старой функции.Приведение указателя - грязный метод ИМХО.Я бы сделал это немного по-другому: я бы связался с сигнатурой функции:
typedef void (*TFPTR_VertexOffset)(GLint, GLenum, GLsizei, uintptr_t);
TFPTR_VertexOffset myglVertexOffset = (TFPTR_VertexOffset)glVertexPointer;
Теперь вы можете использовать myglVertexOffset
без каких-либо глупых приведений, и параметр смещения будет передан функциибез какой-либо опасности, что компилятор может связываться с ним.Это также тот самый метод, который я использую в своих программах.