Re: [Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
mark at codesourcery dot com [EMAIL PROTECTED] writes: | --- Comment #22 from mark at codesourcery dot com 2006-10-04 05:39 --- | Subject: Re: [4.0/4.1/4.2 Regression] placement new | does not change the dynamic type as it should | | ian at airs dot com wrote: | --- Comment #21 from ian at airs dot com 2006-10-03 23:44 --- | In C a general allocation function should work with a char array. A specific | allocation function should use a union. I don't think there are many existing | exceptions to these guidelines. | | So I don't see a serious problem in C either. Am I missing something/ | | I think there are two remaining issues: | |int i; |*(float*)(i) = 7.0; | | IIUC, Mike's position is that this is valid -- and that, in fact, after | this point i can no longer be accessed as an int. Do you agree? I don't see how that code is supported by the paragraphs quoted by Mike. There was a recent discussion (at most two weeks ago) on the C++ standard reflector -core about similar topic. I do agree with Mark that the proper way to resolve this is to go to the C++ committee. It would be incredible that C++ was less type-strict than C. -- Gaby
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #23 from rguenth at gcc dot gnu dot org 2006-10-09 08:22 --- One point to remember is that C does not allow re-using of storage with a different type (which is what PR29272 is about and why that testcase is invalid). The storage type is either the declared one or the one assigned by virtue of the first assignment (or memcpy). So, int i; float f; memcpy(f, i, sizeof(f)); is valid, it doesn't change fs dynamic type but assigns it the bit-pattern of i. What the C++ standard seems to imply is that the storage type of a bunch of memory (or an object with automatic storage) changes on assignment. So, indeed for C++ re-ordering writes is not allowed, and escaping pointers must be assumed to change dynamic type. So for C++ and 4.2 the best solution looks like to disable type based aliasing completely. For C I'm not sure the behavior the standard mandates is very appealing - at least changing dynamic type via the use of memcpy should be allowed as a GCC extension (like we allow type-punning via the union trick). -- http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #19 from ian at airs dot com 2006-10-03 16:03 --- Mike suggests: it would appear that it is unsafe to reorder writes of otherwise non-conflicting types past each other as type based analysis alone isn't enough to ensure they don't conflict. That would be bad in the general case. It would not be as bad as prohibiting the reorder of reads and writes, but it would be bad. Let's find a way such that we do not have to do that. Fortunately I believe that in a correct program we only have a problem when we can actually see the placement new (can any disprove that)? I personally don't have a problem with saying that placement new is special. When placement new is used, it has to move the pointer into alias set 0. With regard to Mark's comment #15, the problem here is not heap allocation, as I see it. The C standard explains how to use heap allocation: use a char* pointer. The problem is doing something akin to heap allocation without using a char* pointer. -- ian at airs dot com changed: What|Removed |Added CC||ian at airs dot com http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #20 from mark at codesourcery dot com 2006-10-03 16:13 --- Subject: Re: [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should ian at airs dot com wrote: Fortunately I believe that in a correct program we only have a problem when we can actually see the placement new (can any disprove that)? I personally don't have a problem with saying that placement new is special. When placement new is used, it has to move the pointer into alias set 0. What about in C, as opposed to C++? I agree that the obvious special cases are access through char*, arrays of characters, and placement new. But, I'm afraid that there are lots of other allocation functions out there that are not spelled operator new, and requiring decoration for them isn't something that (as far as I know) other compilers require. -- http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #21 from ian at airs dot com 2006-10-03 23:44 --- In C a general allocation function should work with a char array. A specific allocation function should use a union. I don't think there are many existing exceptions to these guidelines. I think that code like that in PR 29272, which casts Node* to void* to Foo*, is an unusual case. And I believe we can handle that code correctly because of the use of memcpy. And if the code didn't use memcpy it would be clearly noncomformant--which isn't to say that we should deliberately break it, but we don't need to try extra hard to make it work. So I don't see a serious problem in C either. Am I missing something/ -- http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #22 from mark at codesourcery dot com 2006-10-04 05:39 --- Subject: Re: [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should ian at airs dot com wrote: --- Comment #21 from ian at airs dot com 2006-10-03 23:44 --- In C a general allocation function should work with a char array. A specific allocation function should use a union. I don't think there are many existing exceptions to these guidelines. So I don't see a serious problem in C either. Am I missing something/ I think there are two remaining issues: int i; *(float*)(i) = 7.0; IIUC, Mike's position is that this is valid -- and that, in fact, after this point i can no longer be accessed as an int. Do you agree? Or do you think that this is (should be?) undefined behavior? I think you are saying that: int i; new (i) float; is valid, and that after this point accessing i as an int is invalid. Is that correct? If so, I assume that you conider the following C code valid as well: int i; float f; memcpy (i, f, sizeof (i)); ? Also, in C++, given: extern void f(int *); void g() { int i; f(i); /* HERE */ } do you believe that at the point marked HERE the dynamic type of i is indeterminate? I think that if we allow the examples above, we have to assume that something may have reset the dynamic type of i here, meaning that we cannot assume that future stores through float * in the function do not alias i. -- http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #16 from mrs at apple dot com 2006-10-02 09:32 --- The dynamic type of the object at i is indeed float and has the value 7.0 (iff align and sizes work out). We permitted this so that can can do: class C { ... }; char buf[1024]; new (buf[n]) C and have the dynamic type of the storage be C. You can know the dynamic type is C, by doing a virtual dispatch, or by asking for the name of the type with rtti. Likewise, you can do: new (i) float(7.0) with the same effects. The dynamic type is float, and the value is 7.0, and this is in all ways the same as: *(float*)i = 7.0 Thinking about this some more, it would appear that it is unsafe to reorder writes of otherwise non-conflicting types past each other as type based analysis alone isn't enough to ensure they don't conflict. It might be reasonable to add the C rules that the dynamic type _must_ match the static (declared) type, if there is one for types other than pointer or pointer to member the the language standard and to clarify the dynamic type of an object when a bitwise copy of the object is made to better match the C rules. Reads should be fine as they are required to be the same type as the write (or character). In practice, if the optimizer can't see the placement new, it can't reorder things so this is safe; if it can see it, then it should do value based analysis in addition to type analysis and conclude they conflict (or can conflict) and still avoid doing the wrong thing. I do agree, that for now, this isn't a bug in the library. It could be made a bug in the library by requring a placement new operator as the only way in which storage can be reused as a different dynamic type. -- mrs at apple dot com changed: What|Removed |Added CC||mrs at apple dot com http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #17 from mmitchel at gcc dot gnu dot org 2006-10-02 17:48 --- I disagree with Mike's analysis. I certainly understand the goals of placement new, but I don't think that anyone intended this code: int i; *(float *)i = 7.0; to be valid. I don't even think people intended this variant: int i; new (i) float(7.0); to be valid. Note that a consequence of allowing this is that: int f() { int i = 3; new (i) float(7.0); return i; } is undefined -- not because of the overwriting of i, but because the return statement reads memory of the wrong type. Mike's position is that accessing a variable using its static type is not always safe; I find this bizarre. My personal opinion is that the authors of the standard just didn't think of all the possible interactions of the various aspects. I believe placement new was intended to handle only arrays of characters, consistent with the special dispensation given to character pointers. However, I don't think my differences with Mike can be resolved without going directly to the standards committee. Fortunately, from the compiler point of view, I'm saying punt and Mike is saying punt harder. So, it's probably just a question of in how many cases we decide to disable type-based alias analysis. -- http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #18 from mrs at apple dot com 2006-10-02 19:28 --- What is your position based upon? Mine is based upon having been in the room when we decided what the C rules probably were, what the C++ rules could be and the up side and down side of each choice and where we decided what our intent was going to be and lots of word smithing sessions as well. You can see some of the depth with which we discussed it in our formulation of the wording in the standard: 6 Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any lvalue which refers to the original object may be used but only in limited ways. Such an lvalue refers to allocated storage (_basic.stc.dynamic.deallocation_), and using the properties of the lvalue which do not depend on its value is well-defined. If an lvalue-to-rvalue conversion (_conv.lval_) is applied to such an lvalue, the program has undefined behavior; if the original object will be or was of a non-POD class type, the program has undefined behavior if: --the lvalue is used to access a non-static data member or call a non- static member function of the object, or --the lvalue is implicitly converted (_conv.ptr_) to a reference to a base class type, or --the lvalue is used as the operand of a static_cast (_expr.static.cast_) (except when the conversion is ultimately to char or unsigned char), or --the lvalue is used as the operand of a dynamic_cast (_expr.dynamic.cast_) or as the operand of typeid. 7 If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is cre- ated at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will auto- matically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if: --the storage for the new object exactly overlays the storage location which the original object occupied, and --the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and --the original object was a most derived object (_intro.object_) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects). [Example: struct C { int i; void f(); const C operator=( const C ); }; const C C::operator=( const C other) { if ( this != other ) { this-~C(); // lifetime of *this ends new (this) C(other);// new object of type C created f();// well-defined } return *this; } C c1; C c2; c1 = c2;// well-defined c1.f(); // well-defined; c1 refers to a new object of type C --end example] 8 If a program ends the lifetime of an object of type T with static (_basic.stc.static_) or automatic (_basic.stc.auto_) storage duration and if T has a non-trivial destructor,35) the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place; otherwise the behavior of the program is undefined. This is true even if the block is exited with an exception. We explcitly did consider overlaying one type with a completely different type and did so intend for the standard to allow that. Further, our intent of saying that automatics and objects with static duration could be so changed provided certain conditions were met was meant no only to state that this could be done, but to state the exact sitations in which it was defined to do so. Mike's position is that accessing a variable using its static type is not always safe; I find this bizarre. Welcome to C++, let me quote the standard: 8 If a program ends the lifetime of an object of type T with static (_basic.stc.static_) or automatic (_basic.stc.auto_) storage duration and if T has a non-trivial destructor,35) the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place; otherwise the behavior of the program is undefined. If we didn't mean exactly what we wrote, why on earth would we write it? Hint, in this, we meant exactlty what was written. I was there, in every single meeting where we talked about it. I know what our intent was, and I
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #15 from mmitchel at gcc dot gnu dot org 2006-10-01 18:39 --- I cannot see any plausible way to argue that this is a library bug. Implementation of placement new should not need barriers or other compiler intrinsics. It's twisted to argue that the C++ standard itself would prevent implementation of placement new without non-standard facilities, since the people that wrote the standard all certainly expected the implementation to be just: void *operator new(size_t, void *p) { return p; } Variations on that definition also occur in various user-defined new operators, including those for class-scoped operators. I don't think we can reasonably ask people to change this code, which works with every other C++ compiler. Therefore, the only possible conclusion is that either the compiler is buggy or that the examples are invalid. Unfortunately, I don't think Andrew's example is invalid. There are two possible approaches to fixing the compiler: either (a) the C++ front end should mark all new operators as special, in some way that the middle end then uses to avoid this over-optimization, or (b) relax the aliasing rules used by the middle end. I think the right solution is (b). Fundamentally, I'm not sure that the object models in C and C++ are sufficiently well-defined to permit the kind of optimization that we are discussing. I also think that people use functions not named operator new to do the kinds of things being discussed (i.e., to allocate blocks of memory) and that C and C++ are intended to support that usage. Certainly, in C, people can't use operator new. So, I'd probably suggest that we relax the aliasing rules, at least for heap-allocated objects. As an example of the kind of problems the standard poses, [basic.life] says: The lifetime of an object is a runtime property of the object. The lifetime of an object of type T begins when: --storage with the proper alignment and size for type T is obtained, and --if T is a class type with a non-trivial constructor (_class.ctor_), the constructor call has completed. The lifetime of an object of type T ends when: --if T is a class type with a non-trivial destructor (_class.dtor_), the destructor call starts, or --the storage which the object occupies is reused or released. So, by that definition, it might seem that: int i = 3; *((float *)(i)) = 7.0; is valid; we're reusing the int memory, as a float, and it has the right size and alignment (on most systems). But, that would effectively nullify the [basic.lval] language that suggests that you can't access an object through a type other than its dynamic type -- or, at least, it would limit such access to writes. I don't think anyone would expect the dynamic type of i to be float in this example, even if someone had written: new (i) float; Surely, the dynamic type of something explicitly declared int must be int. But, for something with dynamic storage duration, perhaps we should assume that any write to the storage, with a type other than the one that it already has, forces it to possibly alias things of both types. -- http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
-- mmitchel at gcc dot gnu dot org changed: What|Removed |Added Priority|P3 |P1 http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #13 from pinskia at gcc dot gnu dot org 2006-09-29 16:58 --- And here is a testcase that fails also for 4.0.0 and self contained also: #include new int foo(int n, int *f, long *f1) { long t = 0; *f = 1; long *fp = new(f) long; *fp = 1; for (int i=0; in; ++i) { t += *fp; int *b = new (f) int; *b = i + *f1; fp = new (f) long; *fp = t*t; } t+=*fp; return t+*f1; } extern C void abort (); int main(void) { if (sizeof(int) != sizeof(long) return 0; int *a = new int; long *b = new long; if (foo(2, a, b) != 6) abort (); return 0; } -- pinskia at gcc dot gnu dot org changed: What|Removed |Added Known to fail||4.0.0 4.1.0 4.2.0 Known to work|4.1.1 |3.4.0 Summary|[4.2 Regression] placement |[4.0/4.1/4.2 Regression] |new does not change the |placement new does not |dynamic type as it should |change the dynamic type as ||it should http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286
[Bug libstdc++/29286] [4.0/4.1/4.2 Regression] placement new does not change the dynamic type as it should
--- Comment #14 from pinskia at gcc dot gnu dot org 2006-09-29 17:01 --- (In reply to comment #13) And here is a testcase that fails also for 4.0.0 and self contained also: replace main with: int main(void) { if (sizeof(int) != sizeof(long)) return 0; int *a = new int; long *b = new long; *a = 0; *b = 0; if (foo(2, a, b) != 6) abort (); return 0; } This is what you get when thinking about 2 steps ahead of yourself. -- http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286