Использование препроцессора для определения указателей

#c #c-preprocessor

#c #c-препроцессор

Вопрос:

В моей программе я выделяю пять матриц NxN, используя одно выделение. Каждый сегмент NxN double s обрабатывается как отдельная матрица. Когда я впервые писал код, я определял указатели на каждую матрицу следующим образом.

 #define MAX_N 1024 // could be anything
double* matricies = aligned_alloc(32, 5 * MAX_N * MAX_N * sizeof(double));
double* MA = matricies;
double* MB = matricies   MAX_N * MAX_N;
double* MC1 = matricies   2 * MAX_N * MAX_N;
double* MC2 = matricies   3 * MAX_N * MAX_N;
double* MC3 = matricies   4 * MAX_N * MAX_N;
  

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

 double* matricies = aligned_alloc(32, 5 * MAX_N * MAX_N * sizeof(double));

#define MA (matricies)
#define MB (matricies   MAX_N * MAX_N)
#define MC1 (matricies   2 * MAX_N * MAX_N)
#define MC2 (matricies   3 * MAX_N * MAX_N)
#define MC3 (matricies   4 * MAX_N * MAX_N)
  

Эти идентификаторы используются в качестве указателей в остальной части программы. Мой вопрос в том, является ли это хорошей практикой? У меня такое ощущение, что это не будет масштабироваться, потому что идентификаторы могут конфликтовать с другим текстом в исходном коде. Вы можете, конечно, #undef по мере необходимости, но это может стать неуклюжим.

Комментарии:

1. В чем причина «использования одного выделения» вместо более простого и очевидного пяти выделений?

2. @RayButterworth Я думаю, это просто микрооптимизация. Одно выделение занимает меньше времени, чем пять.

3. Как правило, помещение кода в макросы — ужасная практика. Все, что вы делаете, это скрываете код за именем, которое следующий пользователь, которому придется поддерживать ваш код, вероятно, примет (неправильно) за не более чем константу. Когда они потратят часы на отладку вашего кода и, наконец, обнаружат, что ваш макрос — это нечто большее, чем просто константа, они будут проклинать вас. Я обещаю вам, что они будут, потому что я проклинал подобные макросы много, много раз.

Ответ №1:

В первую очередь зависит от мнения, будете ли вы использовать тот или иной способ. Подход «определяет» сэкономит немного памяти, поскольку вам не понадобятся переменные указателя. Однако компилятор может оптимизировать переменные из кода, так что … возможно, в любом случае ничего не сохранено. На мой взгляд, подход «defines» усложняет понимание и поддержку кода, поэтому я бы не использовал defines.

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

 #define MAX_N 1024

int main(){
    double* matricies = aligned_alloc(32, 5 * MAX_N * MAX_N * sizeof(double));
    double* MA = matricies;
    double* MB = matricies   MAX_N * MAX_N;

    MA[0][3] = 42.1;          // error
    MB[2][1] = 123.8;         // error

    printf("%fn", MA[0][3]); // error  
    printf("%fn", MB[2][1]); // error  

    return 0;
}
  

Этот код приводит к ошибкам компилятора, таким как:

 main.cpp:12:10: error: subscripted value is neither array nor pointer nor vector
     MA[0][3] = 42.1;
  

потому что MA это указатель на double. Для доступа к данным с помощью MA[..][..] вам нужен другой тип для MA . Это может быть «указатель на массив double». Нравится:

 #define MAX_N 1024

int main(){
    void* matricies = aligned_alloc(32, 5 * MAX_N * MAX_N * sizeof(double));
    double (*MA)[MAX_N] = matricies;
    double (*MB)[MAX_N] = MA   MAX_N;


    MA[0][3] = 42.1;
    MB[2][1] = 123.8;

    printf("%fn", MA[0][3]);  
    printf("%fn", MB[2][1]);  

    return 0;
}
  

Этот код компилируется без ошибок, и к матрицам NxN можно получить доступ, как и следовало ожидать. Наконец, ИМО также легче читать и понимать, чем ваш оригинальный «определяющий подход».

Комментарии:

1. Я не знал, как объявлять подобные многомерные массивы, что имеет смысл по сравнению с препроцессором

2. Если бы вам нужен был указатель на начало MA , вы бы предпочли MA[0] или amp;MA[0][0] ? Они оба работают, но с точки зрения удобства чтения, что бы вы использовали?

3. @BradyDean Ну, их значение одинаковое, но они разных типов. Поэтому я бы использовал форму, соответствующую нужному мне типу.

Ответ №2:

является ли это хорошей практикой или нет?

#define MC3 (matricies 4 * MAX_N * MAX_N) это не очень хорошая идея. Код скрывается необычный подход к кодированию приводит к увеличению затрат на обслуживание.


Обратите внимание на проблему с 5 * MAX_N * MAX_N . Это делается с помощью int математики, но индексацию и определение размера массива лучше всего выполнять с помощью size_t математики. Исходный код может переполниться, если MAX_N > 21000 рекомендовать 1) ведущее умножение на sizeof(double) и 2) использовать u для констант размера, таких как #define MAX_N 1024u 3) не выполнять этот трюк с макросом

Комментарии:

1. 1024UL не так ли?

2. @rici UL было бы лучше, чем U . Еще лучше было бы ((size_t)1024) . Жаль, что мы не можем сделать что-то вроде 1024z .