Неудобства указателей на статические переменные - PullRequest
3 голосов
/ 09 февраля 2010

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

char* p(int x) {
    static char res[512];

    snprintf(res, sizeof(res)-1, "number is %d", x));

    return res;
}

и использовать их повсеместно в качестве аргументов для других функций:

...
some_func( somearg, p(6) );
....

Тем не менее, это «удобство» имеет раздражающий недостаток, помимо того, что он не ориентирован на многопоточность (и, вероятно, имеет много других причин):

some_func( somearg, p(6), p(7) );

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

Итак, мой вопрос:

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

***** ОБНОВЛЕНИЕ 2010-04-20 *****

Бесстыдная вилка: посмотрите на мой собственный ответ здесь

Я думаю, это будет работать, но это также граничит с излишним. Мнения?

Ответы [ 6 ]

8 голосов
/ 09 февраля 2010

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

В вашем случае напишите ваш p как

char* p(char *buffer, size_t max_length, int x) { 
  snprintf(buffer, max_length, "number is %d", x); 
  return buffer; 
} 

и назовите его

char buffer1[512], buffer2[512];   
some_func( somearg, p(buffer1, sizeof buffer1 - 1, 6), p(buffer2, sizeof buffer2 - 1, 7) );   

Этот подход имеет по крайней мере один очевидный недостаток: в общем случае вызывающая сторона не знает заранее, сколько символов ему нужно выделить для буфера. Если доступно хорошее постоянное значение времени компиляции, то это легко, но в более сложных случаях требуются дополнительные усилия, такие как предоставление некоторой функциональности «предварительного вычисления», которая возвращает требуемый размер буфера в качестве значения времени выполнения , (Функция snprintf фактически работает таким образом: вы можете вызвать ее с нулевым указателем буфера и нулевым размером буфера, просто чтобы выполнить фиктивный прогон для определения размера буфера).

2 голосов
/ 09 февраля 2010

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

  • Добавьте дополнительный аргумент к вспомогательным методам, чтобы они могли опционально передавать контейнер для возвращаемого значения (также добавьте аргумент size_of_passed_buffer, если такой размер не может быть установлен неявно). Всякий раз, когда вызывающая сторона предоставляет буфер, используйте его, в противном случае используйте статический буфер.
    Кстати, если буфер, передаваемый вспомогательным методам, является локальными переменными, их распределение будет автоматически (и адекватно) управляться в соответствии с продолжительностью жизни подпрограмм, где вызываются вспомогательные методы.

  • Использование кольцевого буфера, позволяющего заданное количество вызовов до повторного использования элементов буфера.
    Такой буфер также может быть глобальным, т. Е. Использоваться совместно с несколькими «удобными» методами (которые, конечно, также должны a) воспроизводить «хорошо», в потоке и b) делить указатель на следующий доступный байт / элемент в буфере.


Реализация всего этого может показаться

  • много работы (для удобства логики), а также
  • возможно несколько ошибок / проблем

Однако при условии, что

  • буфер (ы) имеет (имеют) адекватный размер, и что
  • логика, использующая эти удобные методы, понимает «правила игры»,

этот шаблон предоставляет упрощенную автоматизированную систему управления кучей , что приятно иметь в C (которая, в отличие от Java, .NET и других систем, не предлагает встроенного управления кучей на основе GC). )

2 голосов
/ 09 февраля 2010

Пусть вызывающая сторона предоставит буфер (и размер буфера). Это потокобезопасно, и буфер обычно может помещаться в стек, так что вы не вносите накладные расходы на выделение кучи.

0 голосов
/ 20 апреля 2010

Я нашел альтернативный метод. Примерно так:

#define INIT(n) \
 int xi = 0; \
 char *x[n]; \

#define MACRO(s) \
 (++xi, xi %= sizeof(x)/sizeof(*x), x[xi] = alloca(strlen(s)+1), strcpy(x[xi], (s)), x[xi])

что я могу назвать так:

INIT(2);
some_func( somearg, MACRO("testing1"), MACRO("testing2"));

Таким образом, буферы находятся в стеке без необходимости освобождения. И это даже потокобезопасно.

0 голосов
/ 09 февраля 2010

Если аргументы для помощников всегда литералы (как в примере), вы можете использовать макрос:

#define P(NUMLIT) ("number is " #NUMLIT)

...
somefunc(somearg, P(6), P(7));
...

Препроцессор создает строку из аргумента макроса NUMLIT и добавляет ее к «число есть» для создания строкового литерала, так же как

"this" " " "string" 

выводится как однострочный литерал, «эта строка».

0 голосов
/ 09 февраля 2010

Короче, нет. C не предоставляет никакой формы автоматического управления кучей, так что вы самостоятельно отслеживаете выделенную память. Стандартное C-подобное решение состоит в том, чтобы вызывающая сторона предоставляла буфер, а не выделяла его внутренне. Хотя это просто перекладывает ответственность за отслеживание памяти, она часто оказывается в более удобном месте. Вы могли бы, я полагаю, заглянуть в Консервативный сборщик мусора Бема , если вы хотите получить форму сборки мусора в C.

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