1

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 Answers2

5

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.)

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
4

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.)

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
  • There's a reason: https://codereview.stackexchange.com/a/223175/200418 https://codereview.stackexchange.com/q/223112/200418 – alx - recommends codidact Jun 30 '19 at 13:48
  • Why can't you use a `static inline` function? – Jonathon Reinhart Jun 30 '19 at 13:50
  • Because that would degrade the array into a pointer (in snprintf). In malloc, the reason is that there are too many types (infinite?) to consider them all – alx - recommends codidact Jun 30 '19 at 13:52
  • 4
    @CacahueteFrito If I were you I'd probably make it a function that takes the array size as another param and then write just a small macro wrapper that extracts the size from the first argument and passes it to the function. You don't need a big macro for this. – Petr Skocik Jun 30 '19 at 13:54
  • @PSkocik I think that the size of the macro is not a concern, as long as it is readable. It allows for a very clear and safe usage – alx - recommends codidact Jun 30 '19 at 13:57
  • 4
    @CacahueteFrito Yeah, that's why you had to ask this question - **you couldn't debug your macros**. It took less than a single day for you to run into a problem you couldn't solve. How many other bugs are lurking in that style of code? – Andrew Henle Jun 30 '19 at 13:59
  • @Andrew Thank you all! I reconsidered that, and moved to a function everything that could be moved. The updated code is in the links. – alx - recommends codidact Jun 30 '19 at 16:00