Структура общих указателей и регистрация обратного вызова. Необработанное значение указателя изменилось, когда обратный вызов вызван по причинам вне меня - PullRequest
1 голос
/ 19 марта 2019

Я реализовал интерфейс для регистрации обратных вызовов, но столкнулся с проблемой, которая мне не известна.

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

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

test.h и test.c содержат интерфейс и реализацию для регистрации обратного вызова соответственно, тогда как test_cb.cpp является основным кодом клиента, использующим этот интерфейс.

test.h

#ifndef __TEST_H__
#define __TEST_H__

#ifdef __cplusplus
extern "C"
{
#endif

typedef void (*TestFnCb)(void *pArgs);

void RegisterCallback(TestFnCb fn, void *pArgs);

void PeriodicActions();

#ifdef __cplusplus
}
#endif

#endif

test.c

typedef struct TestFnData
{
  TestFnCb pFunc;
  void *pArgs;
} TestFnData;

TestFnData fndata; 

void RegisterCallback(TestFnCb fn, void *pArgs)
{
  fndata.pFunc = fn;
  fndata.pArgs = pArgs;
}

void PeriodicActions()
{
  while(1)
  {
    if(fndata.pFunc)
    {
      fndata.pFunc(fndata.pArgs);
    }
    usleep(1000000);
  }
}

test_cb.cpp

#include <iostream>
#include <thread>
#include <memory>
#include "test.h"

class B
{
public:
  virtual void print()
  {
    std::cout << "B::print()\n";
  }
};

class D: public B
{
public:
  void print() override
  {
    std::cout << "D::print()\n";
  }
};

struct TestStruct
{
  std::shared_ptr<B> m1;
  std::shared_ptr<B> m2;
};

TestStruct myStruct;

void ThreadFuncProc()
{
  while(1)
  {
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
  }
}

void MyCallback(void *pArgs)
{
  TestStruct *pStruct = static_cast<TestStruct*>(pArgs);
  std::cout << "MyCallback: pStruct " << pStruct << "\n";
  std::cout << "MyCallback: myStruct.m1.get(): " << pStruct->m1.get() << "\n";
  std::cout << "MyCallback: myStruct.m2.get(): " << pStruct->m2.get() << "\n";
  pStruct->m1->print(); // Segfault here because m1.get() isn't pointing to the correct raw pointer.
  pStruct->m2->print();
}

void TestInit(TestStruct testSt) // This is not.
// void TestInit(TestStruct &testSt) // This is fine.
{
  std::cout << "TestInit: &testSt: " << &testSt << "\n";
  std::cout << "TestInit: testSt.m1.get(): " << testSt.m1.get() << "\n";
  std::cout << "TestInit: testSt.m2.get(): " << testSt.m2.get() << "\n";
  RegisterCallback(MyCallback, &testSt);
}

int main(int argc, char **argv)
{
  myStruct.m1 = std::make_shared<D>();
  myStruct.m2 = std::make_shared<D>();

  std::cout << "myStruct.m1.get(): " << myStruct.m1.get() << '\n';
  std::cout << "myStruct.m2.get(): " << myStruct.m2.get() << '\n';
  TestInit(myStruct);
  std::thread t1(ThreadFuncProc);
  std::thread t2(PeriodicActions);
  t1.join();
  t2.join();
  return 0;
}

Как видите, я создал два общих указателя, каждый из которых инкапсулирует указатель на экземпляр класса D. Затем вызывается TestInit() со структурой, содержащей два общих указателя. Он просто регистрирует MyCallback() как обратный вызов и передачу в структуре. Вот где это становится странным. Если TestInit() определено для принятия TestStruct & вместо TestStruct, все в порядке.

выход

$ gcc -o test.o -c test.c
$ g++ -o test_cb.o -c test_cb.cpp -std=c++11
$ g++ -o main test_cb.o test.c -lpthread
$ ./main
myStruct.m1.get(): 0x6bdc30
myStruct.m2.get(): 0x6bdc50
TestInit: &testSt: 0x7fff9578e470
TestInit: testSt.m1.get(): 0x6bdc30
TestInit: testSt.m2.get(): 0x6bdc50
MyCallback: pStruct 0x7fff9578e470
MyCallback: myStruct.m1.get(): 0x7f303f730700
MyCallback: myStruct.m2.get(): 0x6bdc50
Segmentation fault (core dumped)

Чего я не понимаю, так это то, почему копии m1.get() в TestInit() имеют тот же вывод, что и в main(), но не при вызове в MyCallback(). pStruct явно указывает на копию структуры в TestInit(). Что вызвало изменение инкапсулированного необработанного указателя m1? И почему у m2 нет этой проблемы?

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

Любое понимание будет с благодарностью!

1 Ответ

4 голосов
/ 19 марта 2019

В вашей TestInit() функции:

RegisterCallback(MyCallback, &testSt);

&testSt - это адрес вашего testSt параметра функции, который передается значением , так что адрес больше не действителен, когда возвращается функция TestInit().

...