Объявление, распределение и присвоение массива указателей на указатели функций - PullRequest
1 голос
/ 26 мая 2010

Это мой первый пост, поэтому, пожалуйста, будьте осторожны.

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

Вот этот typedef,


//the void parameter is really an SDL_Event*.  
//but that  is irrelevant for this question.  
typedef void (*event_callback)(void);  

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

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


//initial size of callback vector  
static const int initial_vecsize = 32;  
//our event callback vector  
static event_callback* vec = 0;  
//size  
static unsigned int vecsize = 0;  

void register_event_callback(event_callback func) {  
    if (!vec)  
        __engine_allocate_vec(vec);  
    vec[vecsize++] = func; //error here!  
}  

static void __engine_allocate_vec(engine_callback* vec) {  
    vec = (engine_callback*) malloc(sizeof(engine_callback*) * initial_vecsize);  
}  

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

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

Пожалуйста, переполнение стека, направляй меня.

РЕДАКТИРОВАТЬ: Я не могу понять, как сделать отступ для блоков кода. Это немного смущает ...

РЕДАКТИРОВАТЬ: не имеет значения. Проверил на странице источник некоторых других постов =).

Ответы [ 5 ]

3 голосов
/ 26 мая 2010

Функция распределения должна быть:

static void __engine_allocate_vec(engine_callback** vec) {  
    *vec =  malloc(sizeof(engine_callback) * initial_vecsize);  
}  

и затем:

if (!vec)  
    __engine_allocate_vec(&vec);  

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

1 голос
/ 26 мая 2010

На линии:

vec[vecsize++] = func; //error here!  

Что произойдет, если vecsize равно >= initial_vecsize?

Также __engine_allocate_ve не работает, так как он изменяет только локальную копию vec, вы должны изменить подпись на ** и передать аргумент с помощью &.

static void __engine_allocate_vec(engine_callback** vec)

__engine_allocate_vec(&vec);

0 голосов
/ 26 мая 2010

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

Все остальные указали правильный способ изначально выделить вектор:

static void engine_allocate_vec(event_callback **vec)
{
  *vec = malloc(sizeof **vec * initial_vecsize);
}

Обратите внимание на пару вещей. Прежде всего, я не разыгрываю результат malloc. В этом нет необходимости (во всяком случае, в C89; в более ранних версиях C требуется приведение, как в C ++), и он может потенциально подавить диагностику компилятора, если вы забудете включить stdlib.h или у вас нет прототипа для malloc по объему. Во-вторых, я вызываю sizeof для выражения **vec, а не для типа; поскольку тип выражения **vec равен event_callback, sizeof **vec возвращает тот же результат, что и sizeof (event_callback). Это помогает сократить визуальный беспорядок и позволяет избежать довольно распространенной ошибки, которая появляется, когда кто-то меняет тип переменной, но не переносит это изменение в выражение sizeof в вызове malloc, например

double *f; /* was originally declared as float, later changed to double */
...
f = malloc(sizeof (float) * size); /* type change not carried through */

Обратите внимание, что sizeof не оценивает его операнд (если это не VLA), поэтому вам не нужно беспокоиться о вызове его для выражения неинициализированного указателя.

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

static int engine_allocate_vec(event_callback **vec, 
  size_t *currentSize, 
  size_t extent)
{
  int success = 0;
  /**
   * Assign the result of realloc to a temporary; realloc returns NULL
   * on failure, and we don't want to risk losing our pointer to the
   * previously allocated memory.  Similarly, we don't update *currentSize
   * unless the call succeeds.  Note that realloc(NULL, size) is equivalent
   * to malloc(size).
   */
  event_callback *tmp = realloc(*vec, sizeof *tmp * (*currentSize + extent));
  if (tmp != NULL)
  {
    *vec = tmp;
    *currentSize += extent;
    success = 1;
  }
  return success;
}

Тогда ваша функция регистрации становится:

/**
 * Adding vector_count variable to keep track of the number
 * of items in the vector as opposed to the physical size
 * of the vector.
 */
static size_t vector_count = 0;

void register_callback_event(event_callback func)
{
  if (!vec)
  {
    /**
     * Initial vector allocation
     */
    if (!engine_allocate_vec(&vec, &vecsize, initial_vecsize))
    {
      /* allocation failed; treating this as a fatal error */
      exit(0);
    }
  }
  else if (vector_count == vecsize)
  {
    /**
     * Need to extend the vector to accomodate 
     * the new callback.  Double the vector size (i.e.,
     * extend it by the current vector size)
     */
    if (!engine_allocate_vec(&vec, &vecsize, vecsize))
    {
      /* extension failed - treating this as a fatal error*/
      free(vec);
      exit(0);
    }
  }

  vec[vector_count++] = func;
}
0 голосов
/ 26 мая 2010

Ваша __engine_allocate_vec функция создает пространство для новых engine_callback с, но она не делает ничего полезного с этим указателем. (Он меняет свою локальную версию vec, которая была передана по значению - поэтому изменения не возвращают его вызывающей стороне. И имя аргумента скрывает имя глобала, так что это тоже не устанавливается.) Поэтому, когда он возвращает Ваш указатель все еще нулевой.

Попробуйте что-то вроде этого ...

static void __engine_allocate_vec(engine_callback** vec) {
    *vec = (engine_callback*) malloc(sizeof(engine_callback) * initial_vecsize);
}

Затем в register_event_callback передайте &vec функции вместо vec.

Или, сделайте функцию недействительной и дайте ей установить глобальное значение. Нет, забудь, что я это сказал.

0 голосов
/ 26 мая 2010

Вы, кажется, malloc -ин основаны на sizeof(engine_callback*), а не sizeof(engine_callback) ...

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