问题
Consider this program:
#include <stdio.h>
struct S {
S() { print(); }
void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
As far as I can tell, there is exactly one S
object constructed here. There is no copy elision taking place: there is no copy to be elided in the first place, and indeed, if I explicitly delete the copy and/or move constructor, compilers continue to accept the program.
However, I see two different pointer values printed. This happens because my platform's ABI returns trivially copyable types such as this one in CPU registers, so there is no way with that ABI of avoiding a copy. clang preserves this behaviour even when optimising away the function call altogether. If I give S
a non-trivial copy constructor, even if it's inaccessible, then I do see the same value printed twice.
The initial call to print()
happens during construction, which is before the start of the object's lifetime, but using this
inside a constructor is normally valid so long as it isn't used in a way that requires the construction to have finished -- no casting to a derived class, for instance -- and as far as I know, printing or storing its value doesn't require the construction to have finished.
Does the standard allow this program to print two different pointer values?
Note: I'm aware that the standard allows this program to print two different representations of the same pointer value, and technically, I haven't ruled that out. I could create a different program that avoids comparing pointer representations, but it would be more difficult to understand, so I would like to avoid that if possible.
回答1:
T.C. pointed out in the comments that this is a defect in the standard. It's core language issue 1590. It's a subtly different issue than my example, but the same root cause:
Some ABIs require that an object of certain class types be passed in a register [...]. The Standard should be changed to permit this usage.
The current suggested wording would cover this by adding a new rule to the standard:
When an object of class type
X
is passed to or returned from a function, if each copy constructor, move constructor, and destructor ofX
is either trivial or deleted, andX
has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. [...]
For the most part, this would permit the current GCC/clang behaviour.
There is a small corner case: currently, when a type has only a deleted copy or move constructor that would be trivial if defaulted, by the current rules of the standard, that constructor is still trivial if deleted:
12.8 Copying and moving class objects [class.copy]
12 A copy/move constructor for class
X
is trivial if it is not user-provided [...]
A deleted copy constructor is not user-provided, and nothing of what follows would render such a copy constructor non-trivial. So as specified by the standard, such a constructor is trivial, and as specified by my platform's ABI, because of the trivial constructor, GCC and clang create an extra copy in that case too. A one-line addition to my test program demonstrates this:
#include <stdio.h>
struct S {
S() { print(); }
S(const S &) = delete;
void print() { printf("%p\n", (void *) this); }
};
S f() { return {}; }
int main() { f().print(); }
This prints two different addresses with both GCC and clang, even though even the proposed resolution would require the same address to be printed twice. This appears to suggest that while we will get an update to the standard to not require a radically incompatible ABI, we will still need to get an update to the ABI to handle the corner case in a manner compatible with what the standard will require.
回答2:
This is not an answer, rather a note on the different behavior of g++ and clang in this case, depending on the -O
optimization flag.
Consider the following code:
#include <stdio.h>
struct S {
int i;
S(int _i): i(_i) {
int* p = print("from ctor");
printf("about to put 5 in %p\n", (void *)&i);
*p = 5;
}
int* print(const char* s) {
printf("%s: %p %d %p\n", s, (void *) this, i, (void *)&i);
return &i;
}
};
S f() { return {3}; }
int main() {
f().print("from main");
}
We can see that clang (3.8) and g++ (6.1) are taking it a bit differently, but both get to the right answer.
clang (for no -O, -O1, -O2) and g++ (for no -O, -O1)
from ctor: 0x7fff9d5e86b8 3 0x7fff9d5e86b8
about to put 5 in 0x7fff9d5e86b8
from main: 0x7fff9d5e86b0 5 0x7fff9d5e86b0
g++ (for -O2)
from ctor: 0x7fff52a36010 3 0x7fff52a36010
about to put 5 in 0x7fff52a36010
from main: 0x7fff52a36010 5 0x7fff52a36010
It seems that they both do it right in both cases - when they decide to skip the register optimization (g++ -O2) and when they go with the register optimization but copy the value to the actual i on time (all other cases).
来源:https://stackoverflow.com/questions/38043288/does-the-c-standard-guarantee-that-a-function-return-value-has-a-constant-addr