Search code examples
cgccarmembedded

GCC for ARM reports warning - potential uninitialized variable


This is the code that generates warning: 'res' may be used uninitialized in this function [-Wmaybe-uninitialized]

lwdtcr_t
lwdtc_cron_parse_multi(lwdtc_cron_ctx_t* cron_ctx, const char** cron_strs, size_t ctx_len, size_t* fail_index) {
    lwdtcr_t res;

    ASSERT_PARAM(cron_ctx != NULL);
    ASSERT_PARAM(cron_strs != NULL);
    ASSERT_PARAM(ctx_len > 0);

    /* Parse all input strings, each to its own cron context structure */
    for (size_t i = 0; i < ctx_len; ++i) {
        if ((res = lwdtc_cron_parse_with_len(&cron_ctx[i], cron_strs[i], strlen(cron_strs[i]))) != lwdtcOK) {
            if (fail_index != NULL) {
                *fail_index = i;
            }
            break;
        }
    }
    return res;
}

Assert param macro is defined as

#define ASSERT_PARAM(c)                     if (!(c)) { return lwdtcERRPAR; }
/* Footprint of function being called inside is */
lwdtcr_t
lwdtc_cron_parse_with_len(lwdtc_cron_ctx_t* ctx, const char* cron_str, size_t cron_str_len)

Compiler

arm-none-eabi-gcc 10.3.1 20210824 (release)
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I am having hard time to understand why this warning? Assert will kick-in in case variable is 0 and return immediately, meaning for loop will always execute at least one round - if it gets to that point of course - meaning res will be initialized at return statement.

There must be a optimization trick - but I'm not aware of at least - any clue?

lwdtcr_t is simple enumeration.

How to reproduce

Run code from here: https://godbolt.org/z/9E1xdv4dc

Removing '-Og' from flags magically works without any error

Minimum example

#include <string.h>
#include <time.h>

typedef enum {
    lwdtcOK = 0x00,                             /*!< Everything is OK */
    lwdtcERR,                                   /*!< Generic error */
    lwdtcERRPAR,                                /*!< Invalid parameter passed to a function */
    lwdtcERRTOKEN,                              /*!< Token value is not valid */
} lwdtcr_t;

typedef struct {
    uint32_t flags;                             /*!< List of all sort of flags for internal use */
} lwdtc_cron_ctx_t;

lwdtcr_t    lwdtc_cron_parse_with_len(lwdtc_cron_ctx_t* ctx, const char* cron_str, size_t cron_str_len);

#define ASSERT_PARAM(c)                     if (!(c)) { return lwdtcERRPAR; }

lwdtcr_t
lwdtc_cron_parse_multi(lwdtc_cron_ctx_t* cron_ctx, const char** cron_strs, size_t ctx_len) {
    lwdtcr_t res;

    ASSERT_PARAM(cron_ctx != NULL);
    ASSERT_PARAM(cron_strs != NULL);
    ASSERT_PARAM(ctx_len > 0);

    /* Parse all input strings, each to its own cron context structure */
    for (size_t i = 0; i < ctx_len; ++i) {
        res = lwdtc_cron_parse_with_len(&cron_ctx[i], cron_strs[i], strlen(cron_strs[i]));
    }
    return res;
}

With flags -Wall -Werror -Wextra -Og

And compiler

arm-none-eabi-gcc 10.3.1 20210824 (release)
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Solution

  • Here it is, in its bare bones (you could have done this simplification yourself before posting):

    int f (unsigned int len) {
        int res;
        if (len == 0) return 99;
        for (unsigned int i = 0; i < len; ++i)
            res = i;
        return res;
    }
    

    This produces a similar warning.

    But if you replace res = i with res = 99, the warning goes away. So it looks like a case of the compiler just not being infinitely clever. It's not worth spending much time on such cases; the simplest fix is to initialise with int res = 0; (with a comment explaining why).