Why `error: jump into statement expression` only sometimes?

693 Views Asked by At

I have two programs that use the same tricks and features, and only one of them compiles.

A) This one compiles, and also works as expected:

#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>


/*
 * int  mallocs(T **restrict p, ptrdiff_t nmemb);
 */
#define mallocs(ptr, nmemb) (                                       \
{                                                                   \
        ptrdiff_t   nmemb_  = (nmemb);                              \
        __auto_type ptr_    = (ptr);                                \
        int         err_;                                           \
                                                                    \
        err_    = 0;                                                \
        if (ptr_ == NULL) {                                         \
                errno   = EINVAL;                                   \
                err_    = EINVAL;                                   \
                goto ret_;                                          \
        }                                                           \
        if (nmemb_ < 0) {                                           \
                *ptr_   = NULL;                                     \
                errno   = EOVERFLOW;                                \
                err_    = -EOVERFLOW;                               \
                goto ret_;                                          \
        }                                                           \
        if (nmemb_ > (PTRDIFF_MAX / (ptrdiff_t)sizeof(**ptr_))) {   \
                *ptr_   = NULL;                                     \
                errno   = EOVERFLOW;                                \
                err_    = EOVERFLOW;                                \
                goto ret_;                                          \
        }                                                           \
                                                                    \
        *ptr_   = malloc(sizeof(**ptr_) * nmemb_);                  \
        if (!(*ptr_))                                               \
                err_    = ENOMEM;                                   \
ret_:                                                               \
        err_;                                                       \
}                                                                   \
)


int main(void)
{
        int *b1;
        int **p;

        int c = getchar();

        p = &b1;
        if (c == 'a')
                p = 0;
        printf("%c\n", c);

        if (mallocs(p, 47))
                goto err;

        b1[4] = 52;
        printf("Hi: %i\n", b1[4]);

        free(b1);

        return  0;
err:
        perror(NULL);
        exit(EXIT_FAILURE);
}

B) This one doesn't even compile (error is shown below):

#include <assert.h>
#include <errno.h>
#include <stdio.h>


#define alx_same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

#define alx_static_assert_array(a)      do                              \
{                                                                       \
        static_assert(!alx_same_type((a), &(a)[0]), "Not an array!");   \
} while (0)

/*
 * int  alx_sbprintf(char buff[restrict], int *restrict written,
 *              const char *restrict format, ...);
 */
#define alx_sbprintf(buff, written, format, ...)        (               \
{                                                                       \
        __auto_type w_  = (written);                                    \
        int         len_;                                               \
        int         err_;                                               \
                                                                        \
        alx_static_assert_array(buff);                                  \
        err_    = 0;                                                    \
                                                                        \
        len_    = snprintf(buff, sizeof(buff), format, ##__VA_ARGS__);  \
        if (w_ != NULL)                                                 \
                *w_ = len_;                                             \
                                                                        \
        if (len_ < 0) {                                                 \
                err_    = -errno;                                       \
                goto ret_;                                              \
        }                                                               \
        if ((unsigned)len_ >= sizeof(buff)) {                           \
                if (w_ != NULL)                                         \
                        *w_ = sizeof(buff) - 1;                         \
                errno   = ENOMEM;                                       \
                err_    = ENOMEM;                                       \
                goto ret_;                                              \
        }                                                               \
ret_:                                                                   \
        err_;                                                           \
}                                                                       \
)


int main(void)
{
        char    b1[10];
        char    b2[BUFSIZ];

        int     w1;
        int     *w2 = NULL;

        if (alx_sbprintf(b1, &w1, "testttt%i", 12))
                printf("Error 1.1\n");
        printf("b1: %s; w1 = %i\n", b1, w1);

        if (alx_sbprintf(b2, w2, "test%s", "testtt"))
                printf("Error 2.1\n");
        printf("b2: %s; w2 = %p\n", b2, w2);

        return  0;
}

Error:

$ gcc -std=gnu17 -Wall -Wextra -Werror  main.c
main.c: In function ‘main’:
main.c:39:3: error: jump into statement expression
   goto ret_;      \
   ^~~~
main.c:70:6: note: in expansion of macro ‘alx_sbprintf’
  if (alx_sbprintf(b2, w2, "test%s", "testtt"))
      ^~~~~~~~~~~~
main.c:48:1: note: label ‘ret_’ defined here
 ret_:         \
 ^~~~
main.c:66:6: note: in expansion of macro ‘alx_sbprintf’
  if (alx_sbprintf(b1, &w1, "testttt%i", 12))
      ^~~~~~~~~~~~
main.c:46:3: error: jump into statement expression
   goto ret_;      \
   ^~~~
main.c:70:6: note: in expansion of macro ‘alx_sbprintf’
  if (alx_sbprintf(b2, w2, "test%s", "testtt"))
      ^~~~~~~~~~~~
main.c:48:1: note: label ‘ret_’ defined here
 ret_:         \
 ^~~~
main.c:66:6: note: in expansion of macro ‘alx_sbprintf’
  if (alx_sbprintf(b1, &w1, "testttt%i", 12))
      ^~~~~~~~~~~~
main.c:48:1: error: duplicate label ‘ret_’
 ret_:         \
 ^~~~
main.c:70:6: note: in expansion of macro ‘alx_sbprintf’
  if (alx_sbprintf(b2, w2, "test%s", "testtt"))
      ^~~~~~~~~~~~
main.c:48:1: note: previous definition of ‘ret_’ was here
 ret_:         \
 ^~~~
main.c:66:6: note: in expansion of macro ‘alx_sbprintf’
  if (alx_sbprintf(b1, &w1, "testttt%i", 12))
      ^~~~~~~~~~~~

Why does only one of them throw that error?

2

There are 2 best solutions below

0
On BEST ANSWER

GNU C does forbid jumping into a statement expression, but your main problem is that expanding the macro results in the ret_ label being duplicated.

You'll want to combine this statement expression with the __label__ extension for declaring scope-local labels:

#include <assert.h>
#include <errno.h>
#include <stdio.h>


#define alx_same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

#define alx_static_assert_array(a)      do                              \
{                                                                       \
        static_assert(!alx_same_type((a), &(a)[0]), "Not an array!");   \
} while (0)

/*
 * int  alx_sbprintf(char buff[restrict], int *restrict written,
 *              const char *restrict format, ...);
 */
#define alx_sbprintf(buff, written, format, ...)        (               \
{                                                                       \
        __label__ ret_; \
        __auto_type w_  = (written);                                    \
        int         len_;                                               \
        int         err_;                                               \
                                                                        \
        alx_static_assert_array(buff);                                  \
        err_    = 0;                                                    \
                                                                        \
        len_    = snprintf(buff, sizeof(buff), format, ##__VA_ARGS__);  \
        if (w_ != NULL)                                                 \
                *w_ = len_;                                             \
                                                                        \
        if (len_ < 0) {                                                 \
                err_    = -errno;                                       \
                goto ret_;                                              \
        }                                                               \
        if ((unsigned)len_ >= sizeof(buff)) {                           \
                if (w_ != NULL)                                         \
                        *w_ = sizeof(buff) - 1;                         \
                errno   = ENOMEM;                                       \
                err_    = ENOMEM;                                       \
                goto ret_;                                              \
        }                                                               \
ret_:                                                                   \
        err_;                                                           \
}                                                                       \
)


int main(void)
{
        char    b1[10];
        char    b2[BUFSIZ];

        int     w1;
        int     *w2 = NULL;

        if (alx_sbprintf(b1, &w1, "testttt%i", 12))
                printf("Error 1.1\n");
        printf("b1: %s; w1 = %i\n", b1, w1);

        if (alx_sbprintf(b2, w2, "test%s", "testtt"))
                printf("Error 2.1\n");
        printf("b2: %s; w2 = %p\n", b2, w2);

        return  0;
}

(I've only copied the rest of the code but the addition of __label__ ret_; makes the code compile.)

7
On

In example B, you invoke the alx_sbprintf macro twice. This causes the ret_ label to be defined twice, leading to the "duplicate label" error.

The labels aren't scoped to the statement expression, they're scoped to the function.

I'm not sure why the same "jump into statement expression" error isn't raised in your first example though.

Theres no reason to use GCC expression statements like this, when a function will work just fine. (Possibly static inline if you want to put it in a header file.)