I have this code:
int main()
{
char buffer[10];
if( buffer ) {
return 1;
}
return 0;
}
which Visual C++ 10 interprets like this: buffer decays to a pointer, then a pointer is compared against null. When this is compiled with /O2 the check gets eliminated and the code gets equivalent to just return 1;.
Is the code above valid? Does Visual C++ compile it right (I mean the decaying part, not the optimization)?
C++11, 6.4/4:
The value of a condition that is an expression is the value of the
expression, contextually converted to bool for statements other than
switch; if that conversion is ill-formed, the program is ill-formed.
So the standard says that the compiler has to perform any implicit conversions at its disposal to convert the array to a boolean. Decaying the array to pointer and converting the pointer to boolean with a test against against equality to null is one way to do that, so yes the program is well-defined and yes it does produce the correct result -- obviously, since the array is allocated on the stack, the pointer it decays to can never be equal to the null pointer.
Update: As to why this chain of two conversions is followed:
C++11, 4.2/1:
An lvalue or rvalue of type “array of N T” or “array of unknown bound
of T” can be converted to a prvalue of type “pointer to T”. The result
is a pointer to the first element of the array.
So, the only legal conversion from an array type is to a pointer to element type. There is no choice in the first step.
C++11, 4.12/1:
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer
to member type can be converted to a prvalue of type bool. A zero
value, null pointer value, or null member pointer value is converted
to false; any other value is converted to true. A prvalue of type
std::nullptr_t can be converted to a prvalue of type bool; the
resulting value is false.
There is an implicit conversion directly from bare pointer to boolean; so the compiler picks that as the second step because it allows the desired result (conversion to boolean) to be immediately reached.
Yes, the conversion from an array type to bool is well-defined by the standard conversions. Quoting C++11, 4/1 (with the relevant conversions highlighted):
A standard conversion sequence is a sequence of standard conversions in the following
order:
— Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion,
and function-to-pointer conversion.
— Zero or one conversion from the following set: integral promotions, floating point promotion, integral
conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to
member conversions, and boolean conversions.
— Zero or one qualification conversion.
A standard conversion sequence will be applied to an expression if necessary to convert it to a required
destination type.
Yes.
if( buffer ) means: check if buffer is not NULL. An array variable points to the start of the array (unless you move it) and is equivalent to a pointer.
The optimization just returns 1 because that buffer is allocated on the stack, so it definitely has a value (pointer to the location on the stack), so it's always true.
You said it yourself :
buffer decays to a pointer
Since the array is on the stack, it can not be NULL (unless something goes wrong, like stack smashing).
Related
After reading about the standard C11 version of Martin Uecker's ICE_P predicate, I tried to implement it in pure C++. The C11 version, making use of _Generic selection is as follows:
#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)
The obvious approach for C++ is to replace _Generic by a template and decltype, such as:
template<typename T> struct is_ice_helper;
template<> struct is_ice_helper<void*> { enum { value = false }; };
template<> struct is_ice_helper<int*> { enum { value = true }; };
#define ICE_P(x) (is_ice_helper<decltype(1? (void *) ((x)*0) : (int *) 0)>::value)
However, it fails the simplest test. Why can't it detect integer constant expressions?
The issue is subtle. The specification for determining the composite type of the conditional expression's pointer operands are similar in C++ to the ones in C, so it starts off looking promising:
(N4659) [expr.cond]
7 Lvalue-to-rvalue, array-to-pointer, and function-to-pointer
standard conversions are performed on the second and third operands.
After those conversions, one of the following shall hold:
[...]
One or both of the second and third operands have pointer type; pointer conversions, function pointer conversions, and qualification
conversions are performed to bring them to their composite pointer
type (Clause [expr]). The result is of the composite pointer type.
[...]
The reduction to the composite pointer type is specified as follows:
(N4659) [expr]
5 The composite pointer type of two operands p1 and p2 having
types T1 and T2, respectively, where at least one is a pointer or
pointer to member type or std::nullptr_t, is:
if both p1 and p2 are null pointer constants, std::nullptr_t;
if either p1 or p2 is a null pointer constant, T2 or T1, respectively;
if T1 or T2 is “pointer to cv1 void” and the other type is “pointer to cv2 T”, where T is an object type or void, “pointer to cv12 void”,
where cv12 is the union of cv1 and cv2;
[...]
So the result of our ICE_P macro is determined by which of the bullets above we land one after checking each in order. Given how we defined is_ice_helper, we know that the composite type is not nullptr_t, otherwise we'd hit the first bullet, and will get an error due to the missing template specialization. So we must be hitting bullet number 3, making the predicate report false. It all seems to hinge on the definition of a null pointer constant.
(N4659) [conv.ptr] (emphasis mine)
1 A null pointer constant is an integer literal with value
zero or a prvalue of type std::nullptr_t. A null pointer
constant can be converted to a pointer type; the result is the null
pointer value of that type and is distinguishable from every other
value of object pointer or function pointer type. Such a conversion is
called a null pointer conversion. Two null pointer values of the same
type shall compare equal. The conversion of a null pointer constant to
a pointer to cv-qualified type is a single conversion, and not the
sequence of a pointer conversion followed by a qualification
conversion. A null pointer constant of integral type can be converted
to a prvalue of type std::nullptr_t.
Since (int*)0 is not a null pointer constant by the definition above, we do not qualify for the first bullet of [expr]/5. The composite type is not std::nullptr_t. Neither is (void *) ((x)*0) a null pointer constant, nor can it be turned into one. Removing the cast (something the definition doesn't allow) leaves us with (x)*0. This is a integer constant expression with value zero. But it is not an integer literal with value zero! The definition of a null pointer constant in C++ deviates from the one in C!
(N1570) 6.3.2.3 Pointers
3 An integer constant expression with the value 0, or such an
expression cast to type void *, is called a null pointer constant. If
a null pointer constant is converted to a pointer type, the resulting
pointer, called a null pointer, is guaranteed to compare unequal to a
pointer to any object or function.
C allows arbitrary constant expressions with value zero to form a null pointer constant, while C++ requires integer literals. Given C++'s rich support for computing constant expressions of a variety of literal types, this seems like a needless restriction. And one that makes the above approach to ICE_P a non-starter in C++.
As I understand, std::nullptr_t can be initialized from nullptr as well as from 0. But at the same time the third initialization below doesn't work, despite 5 has the same type as 0:
#include <memory>
int main()
{
std::nullptr_t null1=0;
std::nullptr_t null2=nullptr;
std::nullptr_t null3=5; // error: cannot convert ‘int’ to ‘std::nullptr_t’ in initialization
}
How does this work? I.e. how does the standard library distinguish 0 from 5 at compilation time, if these literals aren't template arguments?
Can one create a custom class which would similarly distinguish arguments of its constructor at compilation time, not using std::nullptr_t for this?
A nullptr_t can be only assigned the value nullptr or 0 which is implicitly converted.
According to N4296 (page.86):
4.10 Pointer conversions
A null pointer constant is an integer literal with value zero
or a prvalue of type std::nullptr_t. A null pointer constant can be
converted to a pointer type; the result is the null pointer value of
that type and is distinguishable from every other value of object
pointer or function pointer type. [...] A null pointer constant of
integral type can be converted to a prvalue of type std::nullptr_t.
You can not create a similar type within C++ yourself.
std::nullptr_t is implemented as a built-in type and its distinct properties are enforced by the compiler.
EDIT: Fixed paragraph on built-in types. Thanks Yakk!
how does the standard library distinguish 0 from 5 at compilation time, if these literals aren't template arguments?
This has nothing to do with the standard library at all, nullptr_t is a built-in type known to the compiler, and obviously the compiler knows the difference between 5 and 0
Can one create a custom class which would similarly distinguish arguments of its constructor at compilation time, not using std::nullptr_t for this?
In general no.
You can write a type that can be initialized from 0 and not from 5 by making it take an argument of a pointer type, because 0 is a valid null pointer constant but 5 is not. But you couldn't write a type that can be constructed from 3 and not from 5, or anything else like that.
N3337 [conv.ptr]/1: A null pointer constant is an integral constant expression prvalue of integer type that evaluates to
zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the
result is the null pointer value of that type and is distinguishable from every other value of object pointer or
function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the
same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is
a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion. A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t.
0 is a null pointer constant of integral type, so it can be converted to a prvalue of type std::nullptr_t. 5 is not a null pointer constant, so it can't be.
When you use the if statement in C/C++, or any other logical operator, is the operand you pass to the statement cast to an integer for evaluation?
My current understanding was that the operand passed to the statement is cast to an integer to test whether or not it is non-zero (true), and that if you pass a pointer, this can be cast to an integer to evaluate with a 0/null value being defined as false.
I was under the impression that C++'s standard Bool values were simply typedef of an unsigned char with a value of 0 and 1.
Can anyone explain what's actually happening behind the scenes with this behavior?
In C++ bool is a standalone type that has nothing to do with unsigned char. And in C++ language the expression under if is indeed implicitly cast to bool type. Note: to bool specifically, not to int. For scalar types other than bool, the implicit conversion to bool is essentially defined through non-equality comparison to literal 0. I.e. values that compare non-equal are converted to true, while values that compare equal - to false.
In other words, in C++ statement
if (a)
is interpreted as
bool tmp = (bool) a;
// `tmp` is either `true` or `false`
if (tmp)
which for scalar types is interpreted as
bool tmp = a != 0;
if (tmp)
Note that this works "as expected" for pointer types as well. But instead of converting the pointer to int type, it actually works the other way around: it converts literal 0 to the proper pointer type.
For other types it could be something different, like a call to a user-defined conversion operator operator bool().
In C language the expression under if has to have scalar type. And it is implicitly compared to constant 0. Note that this does not involve converting the controlling expression it to int. Comparison to constant 0 is defined separately for all scalar types. E.g. for int it has its natural meaning, while for pointer types it is interpreted as comparison to null pointer. Now, the result of such comparison in C has type int and evaluates to either 0 or 1. That 0 or 1 is what controls what if branch is taken.
In other words, in C statement
if (a)
is immediately interpreted as
int tmp = a != 0;
// `tmp` is either `0` or `1`
if (tmp)
Additionally, your assumption that null pointer produces a zero int value when converted to int type is incorrect. Neither language makes such guarantee. Null pointer is not guaranteed to be represented by zero address value and is not guaranteed to produce zero value when converted to int.
In C++, the condition in an if statement is converted to a bool.
From the C++11 Standard:
6.4 Selection statements
4 The value of a condition that is an initialized declaration in a statement other than a switch statement is the value of the declared variable contextually converted to bool (Clause 4). If that conversion is ill-formed, the program is ill-formed.
A different section, 4.12 Boolean conversion, of the standard talks about conversions to bool.
4.12/1 A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false;
any other value is converted to true. A prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.
Consider the following simple example computing lenght of an array:
#include <iostream>
int a[] = {1, 2, 4};
int main(){ std::cout << sizeof(a)/sizeof(a[0]) << std::endl; }
DEMO
The Standard N4296::8.3.4/7 [dcl.array]
If E is an n-dimensional array of rank i×j×. . .×k, then E appearing
in an expression that is subject to the array-to-pointer conversion
(4.2) is converted to a pointer to an (n−1)-dimensional array with
rank j ×. . .×k.
N4296::4.2/1 [conv.array]
An lvalue or rvalue of type “array of N T” or “array of unknown bound
of T” can be converted to a prvalue of type “pointer to T”. The result
is a pointer to the first element of the array.
So what is the expressions which are the subject of the convertion? Looks like unevaluated operands are not the subject.
http://coliru.stacked-crooked.com/a/36a1d02c7feff41c
I know of the following expressions in which an array is not converted/decayed to a pointer.
When used in a sizeof operator: sizeof(array)
When used in an addressof operator: &array
When used to bind a reference to an array: int (&ref)[3] = array;.
When deducing the typename to be used for instantiating templates.
When used in decltype: decltype(array)
I don't know if anyone can name all the rules off the top of their head, so a community wiki may be appropriate.
The array to pointer conversion occurs in the following contexts. All references are to the C++11 standard.
As part of an implicit conversion sequence selected by overload resolution1
As part of a standard conversion sequence, in contexts where one is allowed
When initializing an object of non-class type from an array ([dcl.init]/16)2
When assigning to an lvalue of non-class type from an array ([expr.ass]/3)
When a prvalue of pointer type is required as the operand to a built-in operator ([expr]/8)
When subscripting into the array ([expr.sub]/1)
When dereferencing a pointer ([expr.unary.op]/1)
With the unary + operator ([expr.unary.op]/7)
With the binary + operator ([expr.add]/1)
With the binary - operator ([expr.add]/2)
With the relational operators ([expr.rel]/1)
With the equality operators ([expr.eq]/1)
When calling a function, if an argument has array type and is passed to an ellipsis ([expr.call]/7)
When converting from a pointer to base class to a pointer to derived class ([expr.static.cast]/11)
In a reinterpret cast to a non-reference type ([expr.reinterpret.cast]/1)
In a const cast to a non-reference type ([expr.const.cast]/1)
In the second or third operand of the conditional operator, under certain circumstances ([expr.cond])
In a template argument, if the corresponding (non-type) template parameter has pointer to object type ([temp.arg.nontype]/5)
The array to pointer conversion does not occur in the following contexts:
Where an lvalue (or glvalue) is required
By the unary & operator ([expr.unary.op]/3)
In a static cast to reference type ([expr.static.cast]/2, [expr.static.cast]/3)
In a reinterpret cast to reference type ([expr.reinterpret.cast]/11)
In a const cast to reference type ([expr.const.cast]/4)
When binding to a reference to the same array type
In a discarded-value expression ([expr]/10)
In the operand to sizeof ([expr.sizeof]/4)
When the second and third operands to the conditional operator have the same array type and are both glvalues of the same value category
In either operand to the built-in comma operator
1 This includes the case where an array of T is passed to a function expecting cv T*, cv void*, or bool, when a user-defined conversion requires one of those types, etc.
2 This includes contextual conversions to bool as they occur in if statements and the like.
The rule of thumb I work by is "in any part of an expression that produces a value result that can be stored in a pointer but cannot be stored in an array".
So, for example;
The expression array + 0 converts array to a pointer before doing the addition, and gives a result that is a pointer.
f(array) converts array to a pointer before calling the function f() that accepts a pointer or an array (not a reference).
array[0] is not required to convert array to a pointer (but the
compiler is free to, since it makes no difference on the result of that expression).
sizeof array does not convert array to a pointer (since it doesn't
evaluate array at all, just its size)
The expression p = array converts array to a pointer and that value
is stored in p
I'm sure there are some cases I've missed, but that simple rule works reasonably well. Of course, it is based on an understanding of what an expression is.....
In your example code, a[0] is identical to *(a + 0), and is thus subject to array-to-pointer conversion. See the Built-in subscript operator section here.
I've got an iterator of Things. If I want to convert the current item to a pointer to the item, why does this work:
thing_pointer = &(*it);
But this not:
thing_pointer = reinterpret_cast<Thing*>(it);
This is the compiler error I'm trying to comprehend: http://msdn.microsoft.com/en-us/library/sy5tsf8z(v=vs.90).aspx
Just in case, the type of the iterator is std::_Vector_iterator<std::_Vector_val<Thing,std::allocator<Thing> > >
In
&(*it);
the * is overloaded to do what you logically mean: convert the iterator type to its pointed-to object. You can then safely take the address of this object.
Whereas in
reinterpret_cast<Thing*>(it);
you are telling the compiler to literally reinterpret the it object as a pointer. But it might not be a pointer at all -- it might be a 50-byte struct, for all you know! In that case, the first sizeof (Thing*) bytes of it will absolutely not happen to point at anything sensible.
Tip: reinterpret_cast<> is nearly always the wrong thing.
Obligitory Standard Quotes, emphasis mine:
5.2.19 Reinterpret cast
1/ [...] Conversions that can be performed explicitly using
reinterpret_cast are listed below. No other conversion can be
performed explicitly using reinterpret_cast.
4/ A pointer can be explicitly converted to any integral type large
enough to hold it. [...]
5/ A value of integral type or enumeration type can be explicitly
converted to a pointer. [...]
6/ A function pointer can be explicitly converted to a function
pointer of a different type. [...]
7/ An object pointer can be explicitly converted to an object pointer
of a different type. [...]
8/ Converting a function pointer to an object pointer type or vice
versa is conditionally-supported. [...]
9/ The null pointer value (4.10) is converted to the null pointer
value of the destination type. [...]
10/ [...] “pointer to member of X of type T1” can be explicitly
converted to [...] “pointer to member of Y of type T2” [...]
11/ A [...] T1 can be cast to the type “reference to T2” if an
expression of type “pointer to T1” can be explicitly converted to the
type “pointer to T2” using a reinterpret_cast. [...]
With the exception of the integral-to-pointer and value-to-reference conversions noted in 4/, 5/ and 11/ the only conversions that can be performed using reinterpret_cast are pointer-to-pointer conversions.
However in:
thing_pointer = reinterpret_cast<Thing*>(it);
it is not a pointer, but an object. It just so happens that this object was designed to emulate a pointer in many ways, but it's still not a pointer.
Because * operator of iterator is overloaded and it return a
reference to the object it points on.
You can force it by thing_pointer = *(reinterpret_cast<Thing**>(&it));. But it's undefined behavior.
Because iterator is not a pointer. It is a class of implementation-defined structure, and if you try to reinterpret it to a pointer, the raw data of the iterator class will be taken as a memory pointer, which may, but probably will not point to valid memory
The first gets a reference to the object, then takes the address of it, giving the pointer.
The second tries to cast the iterator to a pointer, which is likely to fail because most types can't be cast to pointers - only other pointers, integers, and class types with a conversion operator.