'else' без предыдущего 'if' ошибка при определении макроса с аргументами - PullRequest
3 голосов
/ 01 мая 2019

Рассмотрим код C ниже.

#include <stdio.h>
#define foo(x) if (x>0) printf("Ouch\n");

int main()
{
    int a = 4;
    int b = -3;

    if(a>b)
        foo(b) ;
    else
        printf("Arrg!\n");
    printf("thanks\n");

    return 0;
}

При запуске программы появляется сообщение об ошибке error: ‘else’ without a previous ‘if’.Когда мы заменяем foo(b) на if (b>0) printf("Ouch\n") в соответствии с определением макроса, разве программа не должна переходить в следующий код при написании в фигурных скобках?

if(a>b){
    if(x>0){
        printf("Ouch\n");
    }
}
else{
    printf("Arrh!\n");
}
printf("thanks\n");    

Я не понимаю, почему компилятор жалуется.На что действительно переносится программа?

Спасибо

Ответы [ 4 ]

7 голосов
/ 01 мая 2019

Нет, это превращается в этот код:

if (a > b)
        if (b > 0) printf("Ouch\n");;
    else
        printf("Arrg!\n");

Обратите внимание, что после ("Ouch\n") стоит две точки с запятой. Это то, что нарушает код. else следует за второй точкой с запятой, которая является нулевым, а не if до этого. Причиной этого является то, что у вас есть точка с запятой в вашем определении макроса и еще одна, где вы вызываете макрос.

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

5 голосов
/ 01 мая 2019

Проблема в том, что ваш макрос расширяется до

if(a>b)
    if (b>0) printf("Ouch\n"); ; 
else 
    printf("Arrg!\n");
//...

и это не будет работать с else из-за дополнительных ;. (Также можно заключить в скобки аргумент x (#define foo(x) if ((x)>0) printf("Ouch\n"))).

Если вы потеряете ; из макроса, вы получите другой анализ: else будет совпадать с внутренним if следующим образом:

if(a>b){ /*braces inserted to show the interpretation*/
    if (b>0) printf("Ouch\n");
    else printf("Arrg!\n");
}

и пока вы можете решить проблему, сделав макрос с висящим else

#define foo(x) if ((x)>0) printf("Ouch\n"); else
//a ; after the macro else would complete the `else` with an empty branch

при использовании этого, особенно внутри других if - else, запускает предупреждения в компиляторах, таких как gcc / clang при компиляции -Wall и аналогичных параметрах, поэтому лучший способ справиться с этим - идиоматический

#define macro() do{/*macro_body*/}while(0)

В вашем случае

#define foo(x) do{ if ((x)>0) printf("Ouch\n"); }while(0)

Некоторым людям нравится всегда использовать составные операторы (окруженные { }) с if / else операторами, и хотя это также решит проблему, я чувствую, если вы создаете функциональные макросы для других людей использовать, лучше не навязывать им стиль.

4 голосов
/ 01 мая 2019

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

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

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

Использование реальных функций также решает все эти причудливые крайние случаи, такие как:

#define calc(x) x * x
:
int a = 7;
int b = calc(a + 1);   // a + (1 * a) + 1, NOT (a + 1) * (a + 1)

Если вы хотите, чтобы это работало надежно, вам нужно, чтобы макрос был что-то вроде ((x) * (x)), но даже , что потерпит неудачу с большей сложностью, например b = calc(a++).


Короче говоря, то, что вы должны иметь в своем коде:

void foo(int x) {
    if (x > 0)
        printf("Ouch\n");
}

Если вам do необходимо иметь возможность использовать макросы функций в любом месте (голый оператор, оператор внутри фигурного блока if, оператор в свободном блоке if, фигурный и неразблокированный операторы while и т. д.) приходится прибегать к причудливым макросам, таким как (конечно, #include <stdbool.h> для доступа к false, конечно):

#define XYZZY(s) do {plugh(s);} while (false)

Однако я бы настоятельно предложил бы просто заставить их функционировать.

2 голосов
/ 01 мая 2019

Всегда считается безопасной и эффективной практикой использовать { } после if и else, чтобы избежать таких проблем.

Правильный -

if(a>b) { 
    foo(b) ; /* keep inside { } */
}
else {
    printf("Arrg!\n");
}

не должна программа переходить в следующий код при написании с брекетами?

Нет, компилятор не ставит фигурные скобки вручную. После замены макроса gcc -E test.c выглядит как

int main()
{
    int a = 4;
    int b = -3;

    if(a>b)
        if (b>0) printf("Ouch\n"); ; /* extra semicolon causes the issue */
    else /* there is no if for this else block, previous one terminated by extra ; in above if */
        printf("Arrg!\n");
    printf("thanks\n");

    return 0;
}

пример кода:

#include <stdio.h>
#define foo(x) if ((x)>0) printf("Ouch\n") /* if condition was wrong.. instead of x use (x) */
int main(void)
{
    int a = 4;
    int b = -3;
    if(a>b)
    { /* always keep curly braces even though there is only one statement after if */
        foo(b);
    }
    else
    {
        printf("Arrg!\n");
    }
    printf("thanks\n");
    return 0;
}

Хотя я бы предложил определить MACRO, как показано ниже

#define foo(x) \
    do {                   \
        if((x) > 0)         \
        printf("Ouch\n");  \
    } while(0)
...