Returning a struct from a function - strange behavior

D

DiAvOl

Hello everyone,

Please take a look at the following code:

#include <stdio.h>

typedef struct person {
char name[40];
int age;
} Person;

static Person make_person(void);

int main(void) {
printf("%s\n", make_person().name);

return 0;
}

static Person make_person(void) {
static Person p = { "alexander", 18 };

return p;
}

The above small program when compiled without the -std=c99 option
(using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but argument 2 has type
‘char[40]’"
and also fails with a segmentation fault when executed.

If I replace the line printf("%s\n", make_person().name); with
printf("%s\n", &make_person().name[0]); everything works as expected.

Why does this happen? Isn't make_person().name a pointer to the
array's first element?

Someone replied to this (in the gcc bugzilla), I am quoting the
answer:

"make_person().name is a non-lvalue array, so it only decays to a
pointer
for C99, not for C90. If you use -std=c99/-std=gnu99 then the
program
works.

The program does not, however, have defined behavior for C99, only
for
C1x. In C99 the lifetime of the array ends at the next sequence
point,
before the call to printf. In C1x it instead ends at the end of the
evaluation of the containing full expression, which is the call to
printf.

I do not believe any changes to GCC are needed to implement this
particular C1x requirement, since GCC discards information about
variables
lifetimes smaller than a function for gimplification and tree
optimizations that may change those lifetimes, so it will in practice
treat the lifetime as being anywhere it cannot show the temporary not
to
be live."

I can't understand why make_person().name is not an lvalue array and
only decays to a pointer for C99. Can someone please explain this?

Also what does this guy mean with the line "In C99 the lifetime of the
array ends at the next sequence point,
before the call to printf"? A function call is a sequence point?

I am having a hard time understanding this one, any help appreciated
Thanks for your time

PS. I tried the lcc compiler which compiled the code without warnings/
errors
 
D

DiAvOl

One more question:

Is there any (Person) variable created in main to hold the
make_person() return value?
If this is the case, does all the values from the returned struct be
copied to the variable mentioned above?
 
P

Peter Nilsson

DiAvOl said:
Hello everyone,

Please take a look at the following code:

#include <stdio.h>

typedef struct person {
  char name[40];
  int age;

} Person;

static Person make_person(void);

int main(void) {
  printf("%s\n", make_person().name);
  return 0;
}

static Person make_person(void) {
  static Person p = { "alexander", 18 };
  return p;
}

The above small program when compiled without the
-std=c99 option (using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but
argument 2 has type ‘char[40]’"
and also fails with a segmentation fault when executed.

That's a bug in gcc 4.2.3 then. [The same segfault
happens with -ansi.]
If I replace the line printf("%s\n", make_person().name);
with printf("%s\n", &make_person().name[0]); everything
works as expected.

Why does this happen?

Because there's a bug in gcc 4.2.3.
Isn't make_person().name a pointer to the array's first
element?

Since it's not the operand to an unary & or sizeof operator,
yes.
Someone replied to this (in the gcc bugzilla), I am
quoting the answer:

"make_person().name is a non-lvalue array, so it only
decays to a pointer for C99, not for C90.

Ask them for chapter and verse. And ask them why
printf("%s\n", "hello") isn't ill formed for the same
reason.

C89 (draft):

"An lvalue is an expression (with an object
type or an incomplete type other than void) that
designates an object."

"Except when it is the operand of the sizeof
operator or the unary & operator, or is a character string
literal used to initialize an array of character type, or
is a wide string literal used to initialize an array with
element type compatible with wchar_t, an lvalue that has
type ``array of type '' is converted to an expression that
has type ``pointer to type '' that points to the initial
member of the array object and is not an lvalue.

C99:

6.3.2.1p1 "An lvalue is an expression (with an object
type or an incomplete type other than void) that designates
an object."

6.3.2.1p3 "Except when it is the operand of the sizeof
operator or the unary & operator, or is a string literal
used to initialize an array, an expression that has type
‘‘array of type’’ is converted to an expression with type
‘‘pointer to type’’ that points to the initial element of
the array object and is not an lvalue."

An lvalue is only explicitly required in C90. But in either
case, make_person().name is an lvalue.
 
B

Barry Schwarz

One more question:

Is there any (Person) variable created in main to hold the
make_person() return value?

Officially, it is an implementation detail about which the standard
imposes no requirement.

Practically, if you had ignored the return value, the answer might be
no. Since you use the return value, the answer is almost definitely
yes. But the variable in question is one of those temporary
constructs known only to the compiler. There is no way for you to
access it. Since it has no name, one could argue that it is not a
variable or object.
If this is the case, does all the values from the returned struct be
copied to the variable mentioned above?

Possibly. Maybe even probably. Under the "how would you tell the
difference" concept, the compiler could probably determine you only
need the member name and not copy the member age.

By the way, since the structure is returned by value, there is no need
for it to be static in your function.
 
L

lovecreatesbeauty

int main(void) {
  printf("%s\n", make_person().name);

  return 0;

}

why don't you code it in this way.

int main(void){
Person p;

p = make_person();
printf("%s\n", p.name);

/*printf("%s\n", make_person().name);*/
return 0;
}
 
K

Keith Thompson

Peter Nilsson said:
DiAvOl said:
Hello everyone,

Please take a look at the following code:

#include <stdio.h>

typedef struct person {
  char name[40];
  int age;

} Person;

static Person make_person(void);

int main(void) {
  printf("%s\n", make_person().name);
  return 0;
}

static Person make_person(void) {
  static Person p = { "alexander", 18 };
  return p;
}

The above small program when compiled without the
-std=c99 option (using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but
argument 2 has type ‘char[40]’"
and also fails with a segmentation fault when executed.

That's a bug in gcc 4.2.3 then. [The same segfault
happens with -ansi.]

No, I don't think it's a bug.

[...]
Ask them for chapter and verse. And ask them why
printf("%s\n", "hello") isn't ill formed for the same
reason.

Because "hello" refers to an object of type char[6]; see the
definition of a string literal. There's no object of type Person.

A function returns a value. There isn't necessarily an object
associated with that value. Given:

int func(void) { return 42; }

func returns the value 42; there's no object whose address you could
take, such that dereferencing that address would give you the value
42.

[...]
An lvalue is only explicitly required in C90. But in either
case, make_person().name is an lvalue.

I don't believe it is. make_person() yields a value of type Person.
make_person().age yields a value of type int. make_person().name
reveals an anomaly in the C type system; it should be a value of type
char[40], which should decay to a pointer to the first element of the
corresponding array object, but there is no array object, just a
value.

And this specific case is why the following was added in n1336, the
first C201X draft (6.2.4p7):

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

Footnote 29 says:

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

This new rule means that there is an object of type Person, causing
make_person.name() to be an lvalue. In C90 or C99, it's not an
lvalue.
 
J

jacob navia

DiAvOl said:
Hello everyone,

Please take a look at the following code:

#include <stdio.h>

typedef struct person {
char name[40];
int age;
} Person;

static Person make_person(void);

int main(void) {
printf("%s\n", make_person().name);

return 0;
}

static Person make_person(void) {
static Person p = { "alexander", 18 };

return p;
}

The above small program when compiled without the -std=c99 option
(using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but argument 2 has type
‘char[40]’"
and also fails with a segmentation fault when executed.

If I replace the line printf("%s\n", make_person().name); with
printf("%s\n", &make_person().name[0]); everything works as expected.

Why does this happen? Isn't make_person().name a pointer to the
array's first element?

Someone replied to this (in the gcc bugzilla), I am quoting the
answer:

"make_person().name is a non-lvalue array, so it only decays to a
pointer
for C99, not for C90. If you use -std=c99/-std=gnu99 then the
program
works.

The program does not, however, have defined behavior for C99, only
for
C1x. In C99 the lifetime of the array ends at the next sequence
point,
before the call to printf. In C1x it instead ends at the end of the
evaluation of the containing full expression, which is the call to
printf.

I do not believe any changes to GCC are needed to implement this
particular C1x requirement, since GCC discards information about
variables
lifetimes smaller than a function for gimplification and tree
optimizations that may change those lifetimes, so it will in practice
treat the lifetime as being anywhere it cannot show the temporary not
to
be live."

I can't understand why make_person().name is not an lvalue array and
only decays to a pointer for C99. Can someone please explain this?

Also what does this guy mean with the line "In C99 the lifetime of the
array ends at the next sequence point,
before the call to printf"? A function call is a sequence point?

I am having a hard time understanding this one, any help appreciated
Thanks for your time

PS. I tried the lcc compiler which compiled the code without warnings/
errors


Yes, lcc-win compiles and executes correctly your code. As does
MSVC, that correctly executes it.
 
M

Martin Ambuhl

Nick said:
the point is that it is not obvious why the OPs code is
wrong. It surprised me.

It still surprises me, since _my_ copy of gcc 4.2.3 with -W -Wall
0std=c99 -pedantic neither reports the diagnostic nor eegfaults.
 
D

DiAvOl

An lvalue is only explicitly required in C90. But in either
case, make_person().name is an lvalue.

I don't believe it is.  make_person() yields a value of type Person.
make_person().age yields a value of type int.  make_person().name
reveals an anomaly in the C type system; it should be a value of type
char[40], which should decay to a pointer to the first element of the
corresponding array object, but there is no array object, just a
value.

My question then is if there is no array object why does the
&make_person().name[0] works?

Thanks for your answers
 
N

Nick Keighley

It still surprises me, since _my_ copy of gcc 4.2.3 with -W -Wall
0std=c99 -pedantic neither reports the diagnostic nor eegfaults

so is the program incorrect? Or did a particular version
of gcc have a bug? If the program is exhibiting UB then
translating it and running it to produce the "expected"
behaviour is perfectly ok!
 
M

Martien Verbruggen

It still surprises me, since _my_ copy of gcc 4.2.3 with -W -Wall
0std=c99 -pedantic neither reports the diagnostic nor eegfaults.

The said:
The above small program when compiled without the -std=c99 option

and later
Someone replied to this (in the gcc bugzilla), I am quoting the
answer:

"make_person().name is a non-lvalue array, so it only decays to a
pointer for C99, not for C90. If you use -std=c99/-std=gnu99 then
the program works.

The problem occurs only when compiling the program as a C90 program with
gcc. When compiling it as a C99 program it's fine. Try it with -ansi, or
-std=c89.

Martien
 
V

vippstar

Peter Nilsson said:
DiAvOl said:
Hello everyone,
Please take a look at the following code:
#include <stdio.h>
typedef struct person {
char name[40];
int age;
} Person;
static Person make_person(void);
int main(void) {
printf("%s\n", make_person().name);
return 0;
}
static Person make_person(void) {
static Person p = { "alexander", 18 };
return p;
}
The above small program when compiled without the
-std=c99 option (using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but
argument 2 has type ‘char[40]’"
and also fails with a segmentation fault when executed.

Assuming you have 32bit pointers, the contents of the string are
passed where void * is expected.
It's not a bug in gcc.
I don't believe it is. make_person() yields a value of type Person.
make_person().age yields a value of type int. make_person().name
reveals an anomaly in the C type system; it should be a value of type
char[40], which should decay to a pointer to the first element of the
corresponding array object, but there is no array object, just a
value.

I don't think it's an anomaly.
It's the reason this works as I understand it.

fwrite(&"hello", 1, sizeof "hello", stdout);

&"hello" has type char (*)[6].
When the pointer value is dereferenced as (char *), the correct values
are written; 'h', 'e', etc.
This wouldn't be the case with

char *p = "hello";
fwrite(&p, 1, sizeof "hello", stdout);

For example.

If I'm correct, the value of an array is its contents, at least that's
what appears to be the case in C89, or in GCC's implementation of C89.

Here's a demonstration program: (btw, yes I know I invoke undefined
behavior)

/* assumes 4 byte long */
#include <stdio.h>
struct foo { char test[40]; };
struct foo f(void) { struct foo bar; strcpy(bar.test, "1234"); return
bar; }

int main(void) {

printf("%ld == ", *(long *)"1234");
printf("%ld\n", f().test);

return 0;
}
 
V

vippstar

On Oct 7, 4:53 pm, (e-mail address removed) wrote:
When the pointer value is dereferenced as (char *), the correct values

Or (unsigned char *), but that wasn't the point...
 
N

Nick Keighley

Peter Nilsson said:
Please take a look at the following code:
#include <stdio.h>
typedef struct person {
char name[40];
int age;
} Person;
static Person make_person(void);
int main(void) {
printf("%s\n", make_person().name);
return 0;
}
static Person make_person(void) {
static Person p = { "alexander", 18 };
return p;
}
The above small program when compiled without the
-std=c99 option (using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but
argument 2 has type ‘char[40]’"
and also fails with a segmentation fault when executed.

Assuming you have 32bit pointers, the contents of the string are
passed where void * is expected.

what? Why is thre a void* expected?

It's not a bug in gcc.
I don't believe it is. make_person() yields a value of type Person.
make_person().age yields a value of type int. make_person().name
reveals an anomaly in the C type system; it should be a value of type
char[40], which should decay to a pointer to the first element of the
corresponding array object, but there is no array object, just a
value.

I don't think it's an anomaly.
It's the reason this works as I understand it.

fwrite(&"hello", 1, sizeof "hello", stdout);

&"hello" has type char (*)[6].
When the pointer value is dereferenced as (char *), the correct values
are written; 'h', 'e', etc.
This wouldn't be the case with

char *p = "hello";
fwrite(&p, 1, sizeof "hello", stdout);

I think you're adding confusion. The original code didn't have
array pointers in it. It had arrays.
<snip>
 
V

vippstar

Please take a look at the following code:
#include <stdio.h>
typedef struct person {
char name[40];
int age;
} Person;
static Person make_person(void);
int main(void) {
printf("%s\n", make_person().name);
return 0;
}
static Person make_person(void) {
static Person p = { "alexander", 18 };
return p;
}
The above small program when compiled without the
-std=c99 option (using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but
argument 2 has type ‘char[40]’"
and also fails with a segmentation fault when executed.
Assuming you have 32bit pointers, the contents of the string are
passed where void * is expected.

what? Why is thre a void* expected?

Sorry I was talking about pointers then I thought the conversion
specifier was p :p
It's s, and char * was expected.
It's not a bug in gcc.
I don't believe it is. make_person() yields a value of type Person.
make_person().age yields a value of type int. make_person().name
reveals an anomaly in the C type system; it should be a value of type
char[40], which should decay to a pointer to the first element of the
corresponding array object, but there is no array object, just a
value.
I don't think it's an anomaly.
It's the reason this works as I understand it.
fwrite(&"hello", 1, sizeof "hello", stdout);
&"hello" has type char (*)[6].
When the pointer value is dereferenced as (char *), the correct values
are written; 'h', 'e', etc.
This wouldn't be the case with
char *p = "hello";
fwrite(&p, 1, sizeof "hello", stdout);

I think you're adding confusion. The original code didn't have
array pointers in it. It had arrays.

I was adding to Keiths post, which mentioned array values.
 
K

Keith Thompson

Peter Nilsson said:
Hello everyone,
Please take a look at the following code:
#include <stdio.h>
typedef struct person {
char name[40];
int age;
} Person;
static Person make_person(void);
int main(void) {
printf("%s\n", make_person().name);
return 0;
}
static Person make_person(void) {
static Person p = { "alexander", 18 };
return p;
}
The above small program when compiled without the
-std=c99 option (using gcc 4.2.3) gives me a warning:
"warning: format ‘%s’ expects type ‘char *’, but
argument 2 has type ‘char[40]’"
and also fails with a segmentation fault when executed.

Assuming you have 32bit pointers, the contents of the string are
passed where void * is expected.
It's not a bug in gcc.

That does appear to be what it's doing (with the version I used,
*without* the "-std=c99" option), but I don't see how you conclude
that it's not a bug.

I *think* that the behavior is undefined in both C90 and C99 (but
becomes well defined in C201X).

BTW, in a followup you corrected "void *" to "unsigned char *"; in
fact, printf's "%s" expects "char*".
 
V

vippstar

(e-mail address removed) writes:

That does appear to be what it's doing (with the version I used,
*without* the "-std=c99" option), but I don't see how you conclude
that it's not a bug.

I *think* that the behavior is undefined in both C90 and C99 (but
becomes well defined in C201X).

I don't think so, but it may be true. I'm going to do some reading in
the C99 standard and perhaps find out.
BTW, in a followup you corrected "void *" to "unsigned char *"; in
fact, printf's "%s" expects "char*".

I corrected a different void *. I was talking about fwrite accessing
its void * parameter as (char *).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,994
Messages
2,570,223
Members
46,813
Latest member
lawrwtwinkle111

Latest Threads

Top