Можно ли заставить аргумент функции быть постоянной в C? - PullRequest
0 голосов
/ 15 октября 2018

Есть ли в C способ заставить вызывающую функцию отправить константное значение (например, ожидать ошибки компиляции) в функцию?

Например, рассмотрим эту простую функцию:

int foo(int x, int y)
{
    assert( x < 10);
    return x+y;
}

если я могу как-то сказать компилятору, что x следует отправлять с постоянным значением во всех случаях, assert может быть оценено во время компиляции (даже если assert на самом деле вызывается только во время выполнения).Есть ли способ сделать это?

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

Спасибо.

1 Ответ

0 голосов
/ 15 октября 2018

Прежде всего, вот оно, полностью стандартное решение C11:

#include <stdint.h>
#include <assert.h>

#define is_integral_constant_p(c) (_Generic(0 ? (void*)(uintptr_t)((c) - (c)) : (int*)(0), \
                                  int*: 1,                                            \
                                  default: 0)) 


#define foo(x, y) (is_integral_constant_p(x) ? real_foo : error_foo_x_is_not_a_constant_expression)(x, y)

extern int error_foo_x_is_not_a_constant_expression(int, int);
int real_foo(int x, int y)
{
    assert(x < 10);
    return x+y;
}

int main(void)
{
    static_assert(is_integral_constant_p(5), "5 is not a constant expression?");
    foo(5, 1);

    // int x = 0;
    // foo(x, 1);  // This will not link
}

Волшебство заключается в том, как работает условное выражение.Если один из операндов является константой нулевого указателя, тип результата является типом другого аргумента указателя.В противном случае, если один аргумент равен void*, результат равен void*.Поскольку (void*)(0) является константой нулевого указателя, второй операнд является константой нулевого указателя, только когда (c) - (c) является выражением целочисленной константы, равным 0.Это происходит тогда, когда c является целочисленным константным выражением.

_Generic просто выбирает константу в зависимости от типа результата, который мы ожидаем равным либо int* в истинном случае, либо void* иначе.Более того, сам макрос оценивается как целочисленное константное выражение (и поэтому может даже использоваться в assert).

Выше приведена адаптация метода, используемого в ядре Linux (как обсуждалось ).здесь ), только без специального расширения GNU до C. Используются только функции C11.

Теперь мы реализуем foo в качестве макроса, который проверяет x, и пересылаем в"real_foo", который выполняет вычисления, или функции, которая объявлена, но не определена.Таким образом, программа не может установить связь, если x не является константой.

Смотрите вживую здесь .


Что касается вашего конкретного случая, вы можете удалитьполностью подтвердите утверждение и перенесите проверку в макрос foo.Это выглядело бы примерно так:

#define foo(x, y) (is_integral_constat_p(x) && (x < 10) ? real_foo : \
                   error_foo_x_is_not_a_constant_expression_or_more_than_10)(x, y)

Макрос в сочетании с оценкой короткого замыкания дает нам поведение, которое вы хотите.Как вы можете видеть здесь .

В этот момент я чувствую, что должен вас предостеречь.Этот код может быть не так прост, чтобы защитить в обзоре кода.Поэтому тщательно продумайте, стоит ли какое-либо повышение производительности получить объяснения.

...