Приведение из указателя члена ко всей структуре / классу - PullRequest
3 голосов
/ 21 февраля 2020

Рассмотрим следующий код:

#include <iostream>


struct bar {
  double a = 1.0;
  int b = 2;
  float c = 3.0;
};

void callbackFunction(int* i) {

  auto myStruct = reinterpret_cast<bar*>(i) - offsetof(bar, b);

  std::cout << myStruct->a << std::endl;
  std::cout << myStruct->b << std::endl;
  std::cout << myStruct->c << std::endl;

  //do stuff
}

int main() {

  bar foo;

  callbackFunction(&foo.b);

  return 0;
}

Мне нужно определить функцию обратного вызова, и я хочу использовать некоторую дополнительную информацию в этой функции. Я определил свою собственную структуру и передал адрес члена функции. В функции я хочу «получить» всю структуру путем приведения, но указатели не совпадают, и я получаю неправильные результаты. Я думаю, что я делаю что-то не так во время каста, но я не уверен, что?

Ответы [ 4 ]

4 голосов
/ 21 февраля 2020

Вам не хватает состава, чтобы сделать эту работу. Вам необходимо привести к байтовому типу, прежде чем вычесть смещение, а затем восстановить обратно до bar*. Причина в том, что макрос offsetof возвращает смещение в виде количества байтов. Когда вы делаете указатель арифметики c, вычитание и сложение работают в терминах размера указанного типа. Давайте сделаем пример:

Давайте предположим, что у вас есть экземпляр bar с именем b, который находится по адресу 0x100h. Предполагая, что sizeof(double) == 8, sizeof(int) == 4 и sizeof(float) == 4, тогда sizeof(bar) == 16 и ваша структура и ее члены будут выглядеть в памяти так:

b @ 0x100h
b.a @ 0x100h
b.b @ 0x108h
b.c @ 0x10Ch

offsetof(bar,b) будет равно 8 , Ваш исходный код говорит: «обрабатывайте 0x108h так, как будто он указывает на структуру типа bar. затем дайте мне структуру bar по адресу 0x108h - 8 * sizeof(bar) или, в частности: 0x108h - 0x80h = 88h. ' Надеемся, что пример демонстрирует, почему исходный код выполнял неправильные вычисления.

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

Решение будет выглядеть что-то вроде этого:

bar* owner = reinterpret_cast<bar*>(reinterpret_cast<char *>(i) - offsetof(bar, b));

Одна вещь, к которой вы должны быть очень осторожны: это le git , только если bar равно стандартная компоновка . Вы можете использовать шаблон std::is_standard_layout<bar>::value, чтобы сделать утверждение c, чтобы убедиться, что вы случайно не вызываете UB.

2 голосов
/ 21 февраля 2020

Проблема в том, что с reinterpret_cast<bar*>(i) вы в основном воспринимаете i как указатель на первый элемент массива bar структур.

Это проблематично c, потому что для любого указателя ( или массив) p и индекс i, выражение *(p + i) точно равно p[i].

Таким образом, все выражение reinterpret_cast<bar*>(i) - offsetof(bar, b) в основном похоже на &(reinterpret_cast<bar*>(i))[-offsetof(bar, b)]. То есть вы получаете указатель на элемент -offsetof(bar, b) в этом «массиве». Это, конечно, неверный индекс.

Это сработало бы, если бы у вас был «массив» байт вместо «массива» bar структур:

char* tempPtr = reinterpret_cast<char*>(i) - offsetof(bar, b);
bar* myStructPtr = reinterpret_cast<bar*>(tempPtr);
1 голос
/ 21 февраля 2020

Если вы просто переключаетесь вокруг члена int и double, так что сначала член int, а затем, поскольку ваш класс имеет стандартную компоновку, вы можете просто reinterpret_cast перейти к struct и обычно обращаются к другим членам, потому что первый элемент данных со статусом c и объект класса будут взаимозаменяемыми по указателю :

struct bar {  // Must be standard-layout!
  int b = 2;  // Must be first non-static data member!
  double a = 1.0;
  float c = 3.0;
};

void callbackFunction(int* i) {

  auto myStruct = reinterpret_cast<bar*>(i);

  std::cout << myStruct->a << std::endl;
  std::cout << myStruct->b << std::endl;
  std::cout << myStruct->c << std::endl;

  //do stuff
}


int main() {

  bar foo;

  callbackFunction(&foo.b);

  return 0;
}
1 голос
/ 21 февраля 2020

Вы перемещаете указатель назад слишком сильно, смещение b равно sizeof(double), так что, вероятно, 8, но выражение reinterpret_cast<bar*>(i) - offsetof(bar, b) перемещает его на sizeof(bar) * sizeof(double).

Хотя это технически законно, чтобы разыграть struct/class его 1-го члена, вам никогда не нужно нужно , чтобы сделать это, это может очень легко привести к UB .

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