1

Code as below:

#include"stdio.h"

#define MySTRING(ident, str) \
        ({\
                char str_##ident[16]; \
                memset((char *)str_##ident, 0x00, sizeof(str_##ident)); \
                memcpy(str_##ident, (str), strlen((str))); \
                str_##ident; \
        })

int main(int argc, char **argv)
{
        printf("%u, %u\n", MySTRING(qw, "1.1.1.1"), MySTRING(er, "2.2.2.2"));
}

Tetst result:

[root@cza temp]# gcc -O0 ./fly.c
[root@cza temp]# ./a.out
3959297344, 3959297360

[root@cza temp]# gcc -O2 ./fly.c
[root@cza temp]# ./a.out
2017090240, 2017090240

It seems like gcc optimistic make a difference on it.

The second result is not what I want, but in my app build template, O2 have been set.

I'd like to know the detail on why O2 make it difference, or is it a bug on GCC?

P.S. My colleague told me the prefix "volatile" can work.

cnnbcza
  • 121
  • 1
  • 7

1 Answers1

2

Your code is a gcc extension called Statement Expression. The statements in the braces are executed, and the value of the final statement is the value of the expression. Any objects created are destroyed when the statement ends.

Being in a macro makes no difference; your code (update: the original code) is:

printf("%u, %u\n", ({ char ip_qw[16]; ip_qw; }), ({ char ip_er[16]; ip_er; }) );

When the ip_qw block ends, ip_qw is destroyed, so that memory is freed up for ip_er to use. This explains why it is possible to see the same address for both.

Your code is invalid because the printf function will access the contents of the 16-byte arrays after they have been destroyed.


Fortunately, Standard C has a solution. Objects that are returned by value are guaranteed to hang around until the end of the statement in which the function call was made. Arrays can't be returned by value, but structs can, so we can go:

 #include <stdio.h>
 #include <string.h>

 struct MS
 {
    char str[16];
 };

 struct MS make_MS(char const *str)
 {
    struct MS ms;
    strcpy(ms.str, str);
    return ms;
 }

 #define MySTRING(s) make_MS(s).str

 int main(int argc, char **argv)
 {
     printf("%s, %s\n", MySTRING("1.1.1.1"), MySTRING("2.2.2.2"));
 }

Notes:

  • Use angle brackets for standard includes
  • Use %p to print pointers (makes a difference on 64bit system)
  • Your macro with memset and memcpy was not any safer than strcpy; in robust code you should change this strcpy to a snprintf(ms.str, sizeof ms.str, "%s", str);.
M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    In particular, this is Undefined Behavior because the array decays to a pointer to its first element, and it's not legal to use a pointer to an object after it's been destroyed. The result of this UB happens to manifest itself as two objects which have the same address in memory, but the UB could easily manifest in crashes or other strange behavior, or appearing to work correctly. – Adam Rosenfield Dec 01 '14 at 05:53
  • 1
    it's undefined behaviour because standard C does not have Statement Expressions...:) So I've tried to answer in the context of gcc rather than that of Standard C – M.M Dec 01 '14 at 05:53
  • True, statement expressions are not standard C and the program is not technically legal C in that respect. But even in the extended (ISO C + GCC extensions) model, it's still UB because GCC documents these objects as being temporary and destroyed when the statement expression ends. So doing `char *x = ({char y[16] = {0}; y;});` is just as much UB as `char *foo() { char y[16] = {0}; return y; } ... char *x = foo();`. – Adam Rosenfield Dec 01 '14 at 05:58
  • @MattMcNabb another question, If I define one variable in macro which use do{char a[10];}while(0), Will the variable "a" be destroyed after end of do{}while(0)? Thanks! – cnnbcza Dec 01 '14 at 06:11
  • 1
    @cnnbcza yes, variables are destroyed at the end of the scope they were declared in. In this case the scope is marked by the `{ }` – M.M Dec 01 '14 at 06:15
  • Unfortunately, the return-array-by-struct approach limits you to use a compile time size for the array. Which is bad because it introduces a hard, fixed limit into your code. Almost all of these limits turn into bugs over time. Better remain flexible, return the addresses of `malloc`'ed arrays, and add the extra calls to free them. – cmaster - reinstate monica Dec 01 '14 at 06:23
  • @cmaster if going that far, then VLAs are another option – M.M Dec 01 '14 at 06:28
  • @MattMcNabb Yes, VLAs can be a part of the solution, but they have the problem that they are allocated on the stack, and stack space may be limited itself. It is, for instance, no big deal to `malloc()` a few megabytes to store the lord of the rings, but allocating such a chunk on the stack may well crash your program. That's why I always think twice before I use a VLA instead of a `malloc()`'ed array. VLA pointers are great for multidimensional arrays, though. – cmaster - reinstate monica Dec 01 '14 at 17:18