S
S.Tobias
I'm examining the existence of temporary objects by looking at
their addresses. The trick is to create a structure that contains
an array as its first member. In an expression the array rvalue
is converted to a pointer to its first member. Since this address
is also the address of the array, and that is the address of the
structure, I conclude that this is also the address of the temporary
storage for the structure (r)value.
I'm aware of "Heisenberg effect", ie. in absence of an observer
(".a" operator) the actual behaviour of the operand expressions
might be different. Some behaviours are impossible to measure,
eg. in expression `x = f()' it's impossible to check if `f()'
returns directly into `x', or into a temporary (or other) object,
which is then copied into `x' (I could of course analyze the assembly
listing, but I want to rely only on visible behaviour).
I don't attempt to modify anything.
I would like you to answer my questions and review my conclusions.
The code is at the end, together with the output for como and gcc.
This code compiles only in C99 mode.
1. It is obvious that for some large temporary data a compiler
must sometimes reserve some memory. What does the Standard
say about storage for temporary values? Is it defined (C&V),
undefined or unspecified? What does the Standard say about
the output from my program?
2. Is the expression `f().a[0]' valid? Value of the result of
function call (or any other operator that yields an rvalue) is
accessed, but there's no sequence point, so there doesn't seem to
be an explicit UB in that area. OTOH it is undefined behaviour if
the pointer (f().a + 0) has invalid value (6.5.3.2p4). Even if for
some reason it is valid and there's no object, then it is undefined
behaviour if the lvalue `f().a[0]' does not designate an object when
evaluated (6.3.2.1p1). Anyway, it seems like the answer depends
on whether the temporary thingy for the rvalue is an object or not.
3. In case of sfunc_static() (below) the address of the temporary
could be the same as the static object, because modifying the result
of a fn call raises UB, therefore there is nothing to "protect".
4. Similar argument applies to "=" operator (address "val" could
be the same as that of "s1"). (Conditional and comma operators
in gcc output reuse the operand object storage for the value.)
5. Const object storage can always be reused as storage for its
value, because it is UB if that object is modified.
=== c_temp_obj.c ======================================
#include <stdio.h>
#define PRINT(msg, ptr) printf("%s: \t%p\n", msg, (void*)(ptr))
struct s { int a[1]; };
struct s sfunc()
{
struct s s = {0};
PRINT("inside fn", s.a); /* address of internal variable */
return s;
}
static struct s sfunc_static_obj = {0};
/* sfunc_static() is declared inline so that there is no need
to return through a temporary */
inline static
struct s sfunc_static()
{
PRINT("inside fn", sfunc_static_obj.a); /* address of the static object */
return sfunc_static_obj;
}
inline static
struct s sfunc_ptrarg(struct s *ps)
{
PRINT("inside fn", (*ps).a);
return *ps;
}
int main()
{
struct s s1 = {0}, s2 = {0};
const struct s sc = {0};
puts("\n*&:");
PRINT("s1", &s1);
PRINT("*&s1", (*&s1).a); /* control code, expected equal */
puts("\nfunction call:");
PRINT("fn return", sfunc().a); /* address of the temporary */
puts("\nfunction call (static object):");
PRINT("fn return", sfunc_static().a); /* address of the temporary */
puts("\nfunction call (through argument):");
PRINT("s1", s1.a);
PRINT("fn return", sfunc_ptrarg(&s1).a); /* address of the temporary */
puts("\nassignment operator:");
PRINT("s1", s1.a);
PRINT("s2", s2.a);
PRINT("val", (s1 = s2).a); /* address of the temporary */
puts("\nconditional operator:");
PRINT("s1", s1.a);
PRINT("?:", (1?s1:s2).a);
puts("\ncomma operator:");
PRINT("s1", s1.a);
PRINT("(,)", (0,s1).a);
puts("\ncomma operator, const object:");
PRINT("sc", sc.a);
PRINT("(,)", (0,sc).a);
return 0;
}
=== out.como ==========================================
*&:
s1: 0xbffffa44
*&s1: 0xbffffa44
function call:
inside fn: 0xbffffa00
fn return: 0xbffffa38
function call (static object):
inside fn: 0x8049830
fn return: 0xbffffa34
function call (through argument):
s1: 0xbffffa44
inside fn: 0xbffffa44
fn return: 0xbffffa30
assignment operator:
s1: 0xbffffa44
s2: 0xbffffa40
val: 0xbffffa2c
conditional operator:
s1: 0xbffffa44
?:: 0xbffffa28
comma operator:
s1: 0xbffffa44
(,): 0xbffffa24
comma operator, const object:
sc: 0xbffffa3c
(,): 0xbffffa20
=== out.gcc ===========================================
*&:
s1: 0xbffff224
*&s1: 0xbffff224
function call:
inside fn: 0xbffff1e0
fn return: 0xbffff218
function call (static object):
inside fn: 0x80499f4
fn return: 0xbffff214
function call (through argument):
s1: 0xbffff224
inside fn: 0xbffff224
fn return: 0xbffff210
assignment operator:
s1: 0xbffff224
s2: 0xbffff220
val: 0xbffff20c
conditional operator:
s1: 0xbffff224
?:: 0xbffff224
comma operator:
s1: 0xbffff224
(,): 0xbffff224
comma operator, const object:
sc: 0xbffff21c
(,): 0xbffff21c
their addresses. The trick is to create a structure that contains
an array as its first member. In an expression the array rvalue
is converted to a pointer to its first member. Since this address
is also the address of the array, and that is the address of the
structure, I conclude that this is also the address of the temporary
storage for the structure (r)value.
I'm aware of "Heisenberg effect", ie. in absence of an observer
(".a" operator) the actual behaviour of the operand expressions
might be different. Some behaviours are impossible to measure,
eg. in expression `x = f()' it's impossible to check if `f()'
returns directly into `x', or into a temporary (or other) object,
which is then copied into `x' (I could of course analyze the assembly
listing, but I want to rely only on visible behaviour).
I don't attempt to modify anything.
I would like you to answer my questions and review my conclusions.
The code is at the end, together with the output for como and gcc.
This code compiles only in C99 mode.
1. It is obvious that for some large temporary data a compiler
must sometimes reserve some memory. What does the Standard
say about storage for temporary values? Is it defined (C&V),
undefined or unspecified? What does the Standard say about
the output from my program?
2. Is the expression `f().a[0]' valid? Value of the result of
function call (or any other operator that yields an rvalue) is
accessed, but there's no sequence point, so there doesn't seem to
be an explicit UB in that area. OTOH it is undefined behaviour if
the pointer (f().a + 0) has invalid value (6.5.3.2p4). Even if for
some reason it is valid and there's no object, then it is undefined
behaviour if the lvalue `f().a[0]' does not designate an object when
evaluated (6.3.2.1p1). Anyway, it seems like the answer depends
on whether the temporary thingy for the rvalue is an object or not.
3. In case of sfunc_static() (below) the address of the temporary
could be the same as the static object, because modifying the result
of a fn call raises UB, therefore there is nothing to "protect".
4. Similar argument applies to "=" operator (address "val" could
be the same as that of "s1"). (Conditional and comma operators
in gcc output reuse the operand object storage for the value.)
5. Const object storage can always be reused as storage for its
value, because it is UB if that object is modified.
=== c_temp_obj.c ======================================
#include <stdio.h>
#define PRINT(msg, ptr) printf("%s: \t%p\n", msg, (void*)(ptr))
struct s { int a[1]; };
struct s sfunc()
{
struct s s = {0};
PRINT("inside fn", s.a); /* address of internal variable */
return s;
}
static struct s sfunc_static_obj = {0};
/* sfunc_static() is declared inline so that there is no need
to return through a temporary */
inline static
struct s sfunc_static()
{
PRINT("inside fn", sfunc_static_obj.a); /* address of the static object */
return sfunc_static_obj;
}
inline static
struct s sfunc_ptrarg(struct s *ps)
{
PRINT("inside fn", (*ps).a);
return *ps;
}
int main()
{
struct s s1 = {0}, s2 = {0};
const struct s sc = {0};
puts("\n*&:");
PRINT("s1", &s1);
PRINT("*&s1", (*&s1).a); /* control code, expected equal */
puts("\nfunction call:");
PRINT("fn return", sfunc().a); /* address of the temporary */
puts("\nfunction call (static object):");
PRINT("fn return", sfunc_static().a); /* address of the temporary */
puts("\nfunction call (through argument):");
PRINT("s1", s1.a);
PRINT("fn return", sfunc_ptrarg(&s1).a); /* address of the temporary */
puts("\nassignment operator:");
PRINT("s1", s1.a);
PRINT("s2", s2.a);
PRINT("val", (s1 = s2).a); /* address of the temporary */
puts("\nconditional operator:");
PRINT("s1", s1.a);
PRINT("?:", (1?s1:s2).a);
puts("\ncomma operator:");
PRINT("s1", s1.a);
PRINT("(,)", (0,s1).a);
puts("\ncomma operator, const object:");
PRINT("sc", sc.a);
PRINT("(,)", (0,sc).a);
return 0;
}
=== out.como ==========================================
*&:
s1: 0xbffffa44
*&s1: 0xbffffa44
function call:
inside fn: 0xbffffa00
fn return: 0xbffffa38
function call (static object):
inside fn: 0x8049830
fn return: 0xbffffa34
function call (through argument):
s1: 0xbffffa44
inside fn: 0xbffffa44
fn return: 0xbffffa30
assignment operator:
s1: 0xbffffa44
s2: 0xbffffa40
val: 0xbffffa2c
conditional operator:
s1: 0xbffffa44
?:: 0xbffffa28
comma operator:
s1: 0xbffffa44
(,): 0xbffffa24
comma operator, const object:
sc: 0xbffffa3c
(,): 0xbffffa20
=== out.gcc ===========================================
*&:
s1: 0xbffff224
*&s1: 0xbffff224
function call:
inside fn: 0xbffff1e0
fn return: 0xbffff218
function call (static object):
inside fn: 0x80499f4
fn return: 0xbffff214
function call (through argument):
s1: 0xbffff224
inside fn: 0xbffff224
fn return: 0xbffff210
assignment operator:
s1: 0xbffff224
s2: 0xbffff220
val: 0xbffff20c
conditional operator:
s1: 0xbffff224
?:: 0xbffff224
comma operator:
s1: 0xbffff224
(,): 0xbffff224
comma operator, const object:
sc: 0xbffff21c
(,): 0xbffff21c