0

My TLV structure can hold string or integer. I'm trying to create a macro that handles them both.

The example below runs as expected, but it compiles with warnings from the MACRO expansion. I understand the precompiler cannot know what type of value I'm going to assign at runtime, which is why I think it's raising the warning.

How can this little code snippet be fixed so it generates no compile warnings?

FWIW, I can code around this by not using the MACRO, but would prefer to use it if possible.

$ gcc -o simple{,.c} && ./simple
simple.c: In function ‘main’:
simple.c:25:21: warning: assignment makes pointer from integer without a cast [enabled by default]
       tlv.value_str = (val); \
                     ^
simple.c:38:3: note: in expansion of macro ‘TLV2STR_MACRO’
   TLV2STR_MACRO(string, TYPE_INT, 11);
   ^
simple.c:28:21: warning: assignment makes integer from pointer without a cast [enabled by default]
       tlv.value_int = (val); \
                     ^
simple.c:41:3: note: in expansion of macro ‘TLV2STR_MACRO’
   TLV2STR_MACRO(string, TYPE_STRING, "ELEVEN");
   ^
-----------------------------
INT   : 11
STRING: ELEVEN
-----------------------------
#include <stdio.h>

typedef struct _tlv_s {
  int type;
  size_t length;
  union _value {
    int value_int;
    char *value_str;
  } value_u;
} tlv_t;

#define value_int value_u.value_int
#define value_str value_u.value_str

#define TYPE_STRING 0
#define TYPE_INT 1

#define TLV2STR_MACRO(s, t, val) { \
    tlv_t tlv; \
    tlv.type = (t); \
    if (t == TYPE_STRING) { \
      tlv.value_str = (val); \
      sprintf(s, "STRING: %s", tlv.value_str); \
    } else { \
      tlv.value_int = (val); \
      sprintf(s, "INT   : %d", tlv.value_int); \
    } \
}

int main(int argc, char *argv[])
{
  char string[128];

  printf("-----------------------------\n");
  TLV2STR_MACRO(string, TYPE_INT, 11);
  printf("%s\n", string);

  TLV2STR_MACRO(string, TYPE_STRING, "ELEVEN");
  printf("%s\n", string);
  printf("-----------------------------\n");

}
Matt Muggeridge
  • 465
  • 3
  • 11
  • 1
    Any reason why you can't add a cast to the relevant lines: `tlv.value_str = (char*)(val);` and `tlv.value_int = (int)(val);` ? – Adrian Mole Jul 05 '21 at 11:33
  • Thanks for the tip. I tried that and it fixed one of the cases and the other case has a slightly different warning. I now get: simple.c:25:23: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] tlv.value_int = (int)(val); \ ^ simple.c:38:3: note: in expansion of macro ‘TLV2STR_MACRO’ TLV2STR_MACRO(string, TYPE_STRING, "ELEVEN"); ^ – Matt Muggeridge Jul 05 '21 at 21:00
  • With the clarity of a new morning, I don't think it will be possible to achieve what I first hoped for. Instead, if I modify the macro to pass around pointers to values, then I can cast those pointers. It then corrects the warnings. I'll post my solution. – Matt Muggeridge Jul 05 '21 at 21:16

2 Answers2

0

How can this little code snippet be fixed so it generates no compile warnings?

You can add explicit casts.

tlv.value_str = (char*)(val); \
tlv.value_int = (int)(val); \

To your FWIW, such macro will not scale up, while it is fast to write for a toy example with two types and one use case, it will become painful and unreadable when more to come. Use virtual table with dispatch functions, keep your code readable, prefer not to use macros. I suggest removing confusing definitions #define value_int value_u.value_int and keeping your symbols within one namespace tlv_*. Do not end up with unreadable big switches, which your code seems to be going for. Prefer to use snprintf instead of sprintf.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • All good tips, thank you. I tried your suggestion and the warning morphed to be: simple.c:25:23: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] tlv.value_int = (int)(val); \ ^ simple.c:38:3: note: in expansion of macro ‘TLV2STR_MACRO’. – Matt Muggeridge Jul 05 '21 at 21:05
  • `tlv.value_int = (int)(uintptr_t)(val);` – KamilCuk Jul 05 '21 at 21:11
  • Right, that's the idea. It means I must pass the values as pointers, and not as literal strings or integer values. I'll post my solution and glad for your commentary. – Matt Muggeridge Jul 05 '21 at 21:19
0

I believe (glad to be proven otherwise) that it is not possible to pass values around as I had intended, casting them arbitrarily to string or integer.

Instead, passing pointers to values and casting the pointers is the proper way to do this. (As an aside, if this was truly a TLV implementation, it would handle any kind of structure, but this is just a dinky little app to demo the issue with passing values).

Notice that I modified the macro to accept a pointer to the value.

#include <stdio.h>

typedef struct _tlv_s {
  int type;
  size_t length;
  union _value {
    int value_int;
    char *value_str;
  } value_u;
} tlv_t;

#define value_int value_u.value_int
#define value_str value_u.value_str

#define TYPE_STRING 0
#define TYPE_INT 1

#define TLV2STR_MACRO(s, t, valp) { \
    tlv_t tlv; \
    tlv.type = (t); \
    if (t == TYPE_STRING) { \
      tlv.value_str = (char *)(valp); \
      sprintf(s, "STRING: %s", tlv.value_str); \
    } else { \
      tlv.value_int = *(int *)(valp); \
      sprintf(s, "INT   : %d", tlv.value_int); \
    } \
}

int main(int argc, char *argv[])
{
  char string[128];
  int val_int = 11;

  printf("-----------------------------\n");
  TLV2STR_MACRO(string, TYPE_INT, &val_int);
  printf("%s\n", string);

  TLV2STR_MACRO(string, TYPE_STRING, "ELEVEN");
  printf("%s\n", string);
  printf("-----------------------------\n");

}

And the output from compile and run....

$ gcc -o simple{,.c} && ./simple asdf 
-----------------------------
INT   : 11
STRING: ELEVEN
-----------------------------
Matt Muggeridge
  • 465
  • 3
  • 11