8

After reading the chapter about structures in the K&R book I decided to make some tests to understand them better, so I wrote this piece of code:

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

struct test func(char *c);

struct test
{
    int i ;
    int j ;
    char x[20];
};

main(void)
{
    char c[20];
    struct  {int i ; int j ; char x[20];}  a = {5 , 7 , "someString"} , b; 
    c = func("Another string").x;
    printf("%s\n" , c);
}

struct test func(char *c)
{
    struct test temp;
    strcpy(temp.x , c);
    return temp;    
}

My question is: why is c = func("Another string").x; working (I know that it's illegal, but why is it working)? At first I wrote it using strcpy() (because that seemed the most logical thing to do) but I kept having this error:

structest.c: In function ‘main’:
structest.c:16:2: error: invalid use of non-lvalue array
Farouq Jouti
  • 1,657
  • 9
  • 15
  • 3
    "my question is why is `c = func("Another string").x;` legal" It isn't. At least, not in C. Arrays are not assignable. – Daniel Fischer Aug 23 '13 at 21:27
  • have you tried running the code ? – Farouq Jouti Aug 23 '13 at 21:27
  • 2
    How could I run it if it doesn't compile? clang: `error: array type 'char [20]' is not assignable`, gcc: `error: incompatible types when assigning to type ‘char[20]’ from type ‘char *’` – Daniel Fischer Aug 23 '13 at 21:30
  • 1
    @DanielFischer it actually compiled and ran fine for me, as-is, with `gcc` version 4.7.2. – lurker Aug 23 '13 at 21:33
  • 1
    @DanielFischer there I edited the question , I know it's not legal but why is it working with `gcc` ? – Farouq Jouti Aug 23 '13 at 21:37
  • 1
    @mbratch astonishingly, it does indeed when I don't tell it to adhere to the standard. clang correctly rejects it even in c89 mode. – Daniel Fischer Aug 23 '13 at 21:38
  • @DanielFischer I just ran `gcc` without any particular options, so I didn't ask it to enforce a particular standard. – lurker Aug 23 '13 at 21:39
  • @DanielFischer ah right. A temporary loss of C-sanity... – lurker Aug 23 '13 at 21:40
  • @FaroukJouti what compiler are you using and what options? – lurker Aug 23 '13 at 21:41
  • 3
    @FaroukJouti It's working with gcc because gcc has some weird extensions enabled by default. I have no idea which is responsible for this. – Daniel Fischer Aug 23 '13 at 21:41
  • @mbratch gcc with no options – Farouq Jouti Aug 23 '13 at 21:42
  • 1
    I think @DanielFischer has the answer: some non-standard extensions by default allowed in `gcc`. – lurker Aug 23 '13 at 21:43
  • @DanielFischer here's another thing why doesn't it work when I use the legal way (`strcpy`) – Farouq Jouti Aug 23 '13 at 21:44
  • 1
    @FaroukJouti Can you show the `strcpy` code it rejected? – Daniel Fischer Aug 23 '13 at 21:44
  • @DanielFischer I replace that line with `strcpy(c , function("Another string").x)` – Farouq Jouti Aug 23 '13 at 21:46
  • @DanielFischer are you suggesting that `gcc` is unreliable ? – Farouq Jouti Aug 23 '13 at 21:47
  • 1
    @FaroukJouti Not sure whether it was illegal in C89, but with `-std=c99` or `-std=c11` gcc compiles that (after changing the function name to `func`) fine, even with `-pedantic-errors`. Yes, gcc is, especially if you don't use `-std=cXY` with `XY` 99 or later, not a reliable indicator of what code is conforming. – Daniel Fischer Aug 23 '13 at 21:53
  • @DanielFischer check this out M435tR0x@PrOg:~/c_projects$ gcc -std=c90 structest.c M435tR0x@PrOg:~/c_projects$ gcc -std=c89 structest.c M435tR0x@PrOg:~/c_projects$ gcc -std=c99 structest.c structest.c:12:1: warning: return type defaults to ‘int’ [enabled by default] structest.c: In function ‘main’: structest.c:16:4: error: incompatible types when assigning to type ‘char[20]’ from type ‘char *’ the illegal version works with all `C` standards except c99 – Farouq Jouti Aug 23 '13 at 22:28
  • 2
    speaking with gcc team, it does not seem to be a known bug. Filed a bug: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=58235 – ouah Aug 23 '13 at 22:33
  • That's for the line `c = func("Another string").x;`, just to be sure? Behaves correctly in c99 mode, incorrectly in c89/c90. – Daniel Fischer Aug 23 '13 at 22:33
  • @FaroukJouti ?? `c = func("Another string").x;` is not conforming code. In c99 mode, gcc rejects it, as it should, in c89/c90, it incorrectly accepts it. – Daniel Fischer Aug 23 '13 at 22:37
  • @DanielFischer so what does this mean ? it couldn't possibly by true that it was legal in c89 and c90 standards – Farouq Jouti Aug 23 '13 at 22:39
  • @FaroukJouti Just to be clear, the line `c = func("Another string").x;` was never allowed by any official standard of C, it required a diagnostic message from the first standard (C89) on. That gcc doesn't issue a diagnostic message in c89/c90 mode is a bug. – Daniel Fischer Aug 23 '13 at 22:42
  • @DanielFischer yeah I know I know – Farouq Jouti Aug 23 '13 at 22:43

5 Answers5

6
    char c[20];
    ...
    c = func("Another string").x;

This is not valid C code. Not in C89, not in C99, not in C11.

Apparently it compiles with the latest gcc versions 4.8 in -std=c89 mode without diagnostic for the assignment (clang issues the diagnostic). This is a bug in gcc when used in C89 mode.

Relevant quotes from the C90 Standard:

6.2.2.1 "A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type. and if it is a structure or union. does not have any member (including. recursively, any member of all contained structures or unions) with a const-qualified type."

and

6.3.16 "An assignment operator shall have a modifiable lvalue as its left operand."

6.3.16 is a constraint and imposes at least for gcc to issue a diagnostic which gcc does not, so this is a bug.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
ouah
  • 142,963
  • 15
  • 272
  • 331
  • I'm well aware that it's illegal but why is it working , I mean even if it's a bug in `gcc` why is it returning exactly what I want it to return an not some random things ? – Farouq Jouti Aug 23 '13 at 21:51
  • @FaroukJouti one explanation could be this is allowed in gnu89 and `gcc` "forgets" to disallow it when in c89 mode. – ouah Aug 23 '13 at 21:53
  • is this a known bug ? – Farouq Jouti Aug 23 '13 at 22:07
  • 1
    @FarouqJouti Apparently it was fixed. With gcc 5.8.0 and later it gets a fatal error `error: assignment to expression with array type`, even with `-std=gnu89`. With gcc 4.1.2, it compiles without error and "works", and if you change `char c[20];` to `char c[21];` it complains `error: incompatible types in assignment`. I don't see anything in the gcc 4.1.2 manual about this being a documented extension. – Keith Thompson Jun 11 '20 at 02:02
2

It's a bug in gcc.

An expression of array type is, in most contexts, implicitly converted to a pointer to the first element of the array object. The exceptions are when the expression is (a) the operand of a unary sizeof operator; (b) when it's the operand of a unary & operator; and (c) when it's a string literal in an initializer used to initialize an array object. None of those exceptions apply here.

There's a loophole of sorts in that description. It assumes that, for any given expression of array type, there is an array object to which it refers (i.e., that all array expressions are lvalues). This is almost true, but there's one corner case that you've run into. A function can return a result of struct type. That result is simply a value of the struct type, not referring to any object. (This applies equally to unions, but I'll ignore that.)

This:

struct foo { int n; };
struct foo func(void) {
    struct foo result = { 42 };
    return result;
}

is no different in principle from this:

int func(void) {
    int result = 42;
    return result;
}

In both cases, a copy of the value of result is returned; that value can be used after the object result has ceased to exist.

But if the struct being returned has a member of array type, then you have an array that's a member of a non-lvalue struct -- which means you can have a non-lvalue array expression.

In both C90 and C99, an attempt to refer to such an array (unless it's the operand of sizeof) has undefined behavior -- not because the standard says so, but because it doesn't define the behavior.

struct weird {
    int arr[10];
};
struct weird func(void) {
    struct weird result = { 0 };
    return result;
}

Calling func() gives you an expression of type struct weird; there's nothing wrong with that, and you can, for example, assign it to an object of type struct weird. But if you write something like this:

(void)func().arr;

then the standard says that the array expression func().arr is converted to a pointer to the first element of the non-existent object to which it refers. This is not just a case of undefined behavior by omission (which the standard explicitly states is still undefined behavior). This is a bug in the standard. In any case, the standard fails to define the behavior.

In the 2011 ISO C standard (C11), the committee finally recognized this corner case, and created the concept of temporary lifetime. N1570 6.2.4p8 says:

A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and temporary lifetime Its lifetime begins when the expression is evaluated and its initial value is the value of the expression. Its lifetime ends when the evaluation of the containing full expression or full declarator ends. Any attempt to modify an object with temporary lifetime results in undefined behavior.

with a footnote:

The address of such an object is taken implicitly when an array member is accessed.

So the C11 solution to this quandary was to create a temporary object so that the array-to-pointer conversion would actually yield the address of something meaningful (an element of a member of an object with temporary lifetime).

Apparently the code in gcc that handles this case isn't quite right. In C90 mode, it has to do something to work around the inconsistency in that version of the standard. Apparently it treats func().arr as a non-lvalue array expression (which might arguably be correct under C90 rules) -- but then it incorrectly permits that array value to be assigned to an array object. An attempt to assign to an array object, whatever the expression on the right side of the assignment happens to be, clearly violates the constraint section in C90 6.3.16.1, which requires a diagnostic if the LHS is not an lvalue of arithmetic, pointer, structure, or union type. It's not clear (from the C90 and C99 rules) whether a compiler must diagnose an expression like func().arr, but it clearly must diagnose an attempt to assign that expression to an array object, either in C90, C99, or C11.

It's still a bit of a mystery why this bug appears in C90 mode while it's correctly diagnosed in C99 mode, since as far as I know there was no significant change in this particular area of the standard between C90 and C99 (temporary lifetime was only introduced in C11). But since it's a bug I don't suppose we can complain too much about it showing up inconsistently.

Workaround: Don't do that.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
1

This line

c = func("Another string").x;

with c being declared as

char c[20];

is not valid C in any version of C. If it "works" in your case, it is either a compiler bug or a rather weird compiler extension.

In case of strcpy

strcpy(c, func("Another string").x);

the relevant detail is the nature of func("Another string").x subexpression. In "classic" C89/90 this subexpression cannot be subjected to array-to-pointer conversion, since in C89/90 array-to-pointer conversion applied to lvalue arrays only. Meanwhile, your array is an rvalue, it cannot be converted to const char * type expected by the second parameter of strcpy. That's exactly what the error message is telling you.

That part of the language was changed in C99, allowing array-to-pointer conversion for rvalue arrays as well. So in C99 the above strcpy will compile.

In other words, if your compiler issues an error for the above strcpy, it must be an old C89/90 compiler (or a new C compiler run in strict C89/90 mode). You need C99 compiler to compile such strcpy call.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
0

OP: but why is it working?
Because apparently when copying a field of a structure, only type and size matters.
I'll search for doc to back this up.

[Edit] Reviewing C11 6.3.2 concerning assignments, the LValue C, because it is an array, it is the address of that array that becomes the location to store the assignment (no shock there). It is that the result of the function is a value of an expression, and the sub-field reference is also a value of an expression. Then this strange code is allowed because it simple assigns the value of the expression (20-bytes) to the destination location&c[0], which is also a char[20].

[Edit2] The gist is that the result of the func().x is a value (value of an expression) and that is a legit assignment for a matching type char[20] on the left side. Whereas c = c fails for c on the right side (a char[20]), becomes the address of the array and not the entire array and thus not assignable to char[20]. This is so weird.

[Edit3] This fails with gcc -std=c99.

I tried a simplified code. Note the function func returns a structure. Typical coding encourages returning a pointer to a structure, rather than a whole copy of some big bad set of bytes.

ct = func("1 Another string") looks fine. One structure was copied en masse to another.

ct.x = func("2 Another string").x starts to look fishy, but surprisingly works. I'd expect the right half to be OK, but the assignment of an array to an array looks wrong.

c = func("3 Another string").x is simply like the previous. If the previous was good, this flies too. Interestingly, if c was size 21, the compilation fails.

Note: c = ct.x fails to compile.

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

struct test {
    int i;
    char x[20];
};

struct test func(const char *c) {
  struct test temp;
  strcpy(temp.x, c);
  return temp;
}

int main(void) {
  char c[20];
  c[1] = '\0';
  struct test ct;
  ct = func("1 Another string");
  printf("%s\n" , ct.x);
  ct.x = func("2 Another string").x;
  printf("%s\n" , ct.x);
  c = func("3 Another string").x;
  printf("%s\n" , c);
  return 0;
}

1 Another string
2 Another string
3 Another string
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • well then what do you mean by "Note: c = ct.x fails to compile." – Farouq Jouti Aug 23 '13 at 23:04
  • I see what you mean but why does this only happens with structures and not when a regular array is assigned to another one ? – Farouq Jouti Aug 23 '13 at 23:09
  • "c = ct.x " errors with mangled "incompatible types when assigning to type char[20]’ from type ‘char *’". If `char C[21]` compilation fails with "incompatible types when assigning to type ‘char[21]’ from type ‘char[20]’". (Need to work on De-mangleing). BTW, it is Eclipse IDE for C/C++ Developer 1.4.1.20110909-1818 on Win 7-64 bit. – chux - Reinstate Monica Aug 24 '13 at 00:19
  • 1
    Re: "why does this only happens with structures". It appears to happen with both. I can't do a `ct.x = ct.x` either. I think it is because the RValue, in these cases, is the pointer to the `char` array - thus trying to assign a `char *` to a `char[20]`. The result from the function, the RValue, is a _value of an expression_ (`char[20]` to a `char[20]`). – chux - Reinstate Monica Aug 24 '13 at 00:25
0

There are two error in you code:

main(void)
{
        char c[20];
        struct  { int i ; int j ; char x[20];}  a = {5 , 7 , "someString"} , b; 
        c = func("Another string").x;// here of course number one
        printf("%s\n" , c);
}
struct test func(char *c)
{
        struct test temp;
        strcpy(temp.x , c);
        return temp;         // here is number two , when the func finished the memory of function func was freed, temp is freed also. 

}

Write you code like this:

main(void)
{
        struct test *c;
        struct  { int i ; int j ; char x[20];}  a = {5 , 7 , "someString"} , b; 
        c = func("Another string");
        printf("%s\n" , c->x);
        free(c);                              //free memory
}
struct test * func(char *c)
{
        struct test *temp = malloc(sizeof(struct test));//alloc memory 
        strcpy(temp->x , c);
        return temp;
}
Lidong Guo
  • 2,817
  • 2
  • 19
  • 31