Какой самый элегантный способ сделать нетривиальную инициализацию перед main? - PullRequest
5 голосов
/ 24 октября 2011

Я не очень люблю шаблоны проектирования C, поэтому мои сомнения потенциально просты (хотя и немного конкретны). Реальное применение этого вопроса объяснить сложнее, поэтому позвольте мне упростить его.

Предположим, у меня есть массив, в котором я хочу хранить простые числа. Число простых чисел, содержащихся в этом массиве, определяется как NUMBER_OF_PRIMES, константа, определенная во время компиляции.

Таким образом, имеем:

unsigned primes[NUMBER_OF_PRIMES];

Если размер был зафиксирован, я мог бы предварительно вычислить простые числа и инициализировать массив как обычно:

unsigned primes[NUMBER_OF_PRIMES] = { 2, 3, 5, 7, ... };

Но это было бы довольно уродливо, если бы NUMBER_OF_PRIMES было, скажем, больше 200. Я хочу каким-то образом запустить функцию во время выполнения, но перед main (), которая поместит эти числа простых чисел туда. Конечно, я мог бы сделать:

unsigned* primes = make_primes(NUMBER_OF_PRIMES);

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

/* Header file */
unsigned primes[NUMBER_OF_PRIMES];

/* C file */
int nothing = initialize_primes(); /* This function would write the 
values to the array, using things that are not available in the
header (it is in the .c, so it can reference them), and return anything */

Другой способ - это, очевидно, поместить initialize_primes() в заголовочный файл и попросить пользователя вызвать его внутри основной функции (например, функции init() некоторых библиотек). Однако я бы не хотел, чтобы main() содержал вызов этой функции.

У меня вопрос: есть ли какой-нибудь элегантный способ сделать это, который общепризнан / используется, или если это смешно, и я должен попросить main() вызвать функцию init(), чтобы избежать ненужного, неясного кода.

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

С наилучшими пожеланиями.

Ответы [ 4 ]

7 голосов
/ 24 октября 2011

Использование функции init - правильный путь.Не забудьте также добавить функцию fini , чтобы люди могли освободить память, если захотят (не зная, что делает ваша библиотека, это может или не может быть важным, но в целом хорошо иметь возможностьосвободить всю память - например, когда библиотека используется загружаемым модулем, она обычно не хочет пропускать память при ее выгрузке).

5 голосов
/ 24 октября 2011

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

// primes.c
static unsigned *p = NULL;

const unsigned *primes(void)
{
    if(p == NULL)
      {
        p = malloc((PRIMES_COUNT + 1) * sizeof *p);
        *p++ = 0; // if user accidentally frees p now, it will segfault
        // populate p
        atexit(primes_cleanup);
      }
    return p;
}

void primes_cleanup(void)
{
    if(p)
      {
        free(p - 1); // correct way to free the pointer
        p = NULL;    // now calling _cleanup twice is fine
      }
}

Пользователь использует primes()[N], чтобы получить желаемый элемент, и он гарантированно будет инициализирован.Даже если вы сохраните указатель и будете использовать его позже, он гарантированно сработает (если кто-то не отбросит const и не изменит ваш список, когда вы не искали).Если вам все еще нужен синтаксис, похожий на переменную, используйте это:

// primes.h
const unsigned *primes(void);
void primes_cleanup(void);
#define PRIMES (primes())

Для конечного пользователя PRIMES будет выглядеть как любая другая переменная.Win.

5 голосов
/ 24 октября 2011

Зачем вам нужно запустить до main? Единственная причина, о которой я могу подумать, это то, что вы не контролируете main (т.е. вы просто библиотека, которую кто-то будет вызывать).

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

Если вы делаете , управляете main, тогда просто вызовите свою собственную функцию инициализации прямо в начале. не нужно, чтобы произошло раньше.

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

Он может предварительно рассчитать значения на основе этой константы и создать файл C с массивом, который вы затем сможете скомпилировать и скомпоновать. Это, по крайней мере, избавило бы от необходимости набирать их.

Это также отодвигает стоимость вычислений от пользователя, что приводит к более быстрому запуску (при условии, что стоимость нетривиальна).

1 голос
/ 24 октября 2011

Могут быть некоторые неприятные способы сделать это, однако я бы предложил А) предоставить функцию init, которая будет вызываться как требуется, или Б), если размер статического массива, вы могли бы написать программу в автономном режиме для генерации массива, который включен. Таким образом, вы можете восстановить его позже, если вам нужно изменить его. Но А) это более приемлемый способ сделать это

...