Подтвердить что ты не робот

Почему макросы C не относятся к типу?

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

Итак, почему макросы не безопасны? Это, по-видимому, одна из основных причин, почему их следует считать злом.

4b9b3361

Ответ 1

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

Ответ 2

Рассмотрим типичный макрос "max" в зависимости от функции:

#define MAX(a,b) a < b ? a : b
int max(int a, int b) {return a < b ? a : b;}

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

Если вызывающая функция записывает

char *foo = max("abc","def");

компилятор будет предупреждать.

В то время как если вызывающий макрос пишет:

char *foo = MAX("abc", "def");

препроцессор заменит это следующим образом:

char *foo = "abc" < "def" ? "abc" : "def";

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

Кроме того, конечно, побочные эффекты различны, рассмотрим случай функции:

int x = 1, y = 2;
int a = max(x++,y++); 

функция max() будет работать с исходными значениями x и y, а пост-приращения вступят в силу после возвращения функции.

В случае макроса:

int x = 1, y = 2;
int b = MAX(x++,y++);

чтобы вторая строка была предварительно обработана, чтобы дать:

int b = x++ < y++ ? x++ : y++;

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

Ответ 3

Макросы не безопасны для типов, поскольку они не понимают типы.

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

С помощью функции вы можете определить функцию void f(int, int), и компилятор будет отмечать, если вы попытаетесь использовать возвращаемое значение f или передать его.

С макросом - никаких шансов. Единственные проверки, которые были сделаны, - это дать правильное количество аргументов. то он соответствующим образом заменяет маркеры и переходит к компилятору.

#define F(A, B)

позволит вам вызвать F(1, 2) или F("A", 2) или F(1, (2, 3, 4)) или...

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

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

Ответ 4

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

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

Пример

В API Windows, если вы хотите отобразить подсказку шара в элементе управления редактирования, вы должны использовать Edit_ShowBalloonTip. Edit_ShowBalloonTip определяется как принятие двух параметров: дескриптор элемента управления редактирования и указатель на структуру EDITBALLOONTIP. Однако Edit_ShowBalloonTip(hwnd, peditballoontip); - это фактически макрос, который вычисляет

SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)(peditballoontip));

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

Отклонение

Интересно, что иногда встроенные функции С++ являются слишком безопасными для типов. Рассмотрим стандартный макрос C MAX

#define MAX(a, b) ((a) > (b) ? (a) : (b))

и его встроенную версию С++

template<typename T>
inline T max(T a, T b) { return a > b ? a : b; }

MAX (1, ​​2u) будет работать так, как ожидалось, но max (1, 2u) не будет. (Поскольку 1 и 2u - разные типы, max не может быть создан на обоих из них.)

В большинстве случаев это не аргумент для использования макросов (они все еще злы), но это интересный результат безопасности типа C и С++.

Ответ 5

Бывают ситуации, когда макросы еще менее безопасны по типу, чем функции. Например.

void printlog(int iter, double obj)
{
    printf("%.3f at iteration %d\n", obj, iteration);
}

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

#define PRINTLOG(iter, obj) printf("%.3f at iteration %d\n", obj, iter)

вызывает поведение undefined. Справедливости ради, GCC предупреждает о последнем, но не о первом, но что, поскольку он знает printf - для других функций varargs, результаты могут быть катастрофическими.

Ответ 6

Когда макрос запускается, он просто выполняет текстовое сопоставление через исходные файлы. Это перед любой компиляцией, поэтому она не знает о типах данных, которые она меняет.

Ответ 7

Макросы не являются безопасными по типу, поскольку они никогда не должны были быть безопасными по типу.

В компиляторе был расширен тип проверки после макросов.

Макросы и их расширение подразумеваются как помощник ( "ленивый" ) автор (в смысле автора/читателя) исходного кода C. Это все.