| Topic: |
DEVELOP > c-Plus-Plus |
| User: |
"Ioannis Vranos" |
| Date: |
05 Jan 2008 09:02:09 AM |
| Object: |
Validity of pointer conversions |
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work, since all
arrays are sequences of their elements.
2.
#include <iostream>
int main()
{
using namespace std;
int array[50]= {0};
int (*p)[5]= reinterpret_cast<int (*)[5]> (&array[0]);
for (size_t i= 0; i< 10; ++i)
for(size_t j=0; j<5; ++j)
cout<< p[i][j]<<" ";
cout<< endl;
}
Here p behaves as a 2-dimensional matrix, that is a 10x5 matrix. I think
it is guaranteed to work for the same reason as the first one, that is
we can treat an array (sequence) of integers as various types of integer
arrays.
.
|
|
| User: "Number774" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 02:04:16 PM |
|
|
On 5 Jan, 15:02, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
<snip>
Ioannis,
They aren't guaranteed to work, and I can think of at least one
architecture in which they probably won't.
On the 8086 memory model, an int is usually 16 bits, and a "segment"
of memory is 64Kb - allowing 32k ints. Segments are 16 bytes apart -
a "paragraph" - and can overlap. You can tell the compiler to work in
several ways to get around this limit.
If the compiler is working in such a mode as to give a different
segment address for each of your 10 arrays, each one will be padded up
to the next paragraph boundary, so you'll have 10 bytes for the 5
integers - and then 6 bytes free (almost the same as padding). The
next array will be on the next paragraph boundary. There's no bounds
checking, so the first 5 integers will be the ones you expect, the
next 3 will be the padding, and only then will you get the 2nd 5.
Ciao
.
|
|
|
|
| User: "Salt_Peter" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 11:35:05 AM |
|
|
On Jan 5, 10:02 am, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work, since all
arrays are sequences of their elements.
// Are you sure? try...
int array[10][5]= {99};
// and as far as a function for an array:
template< typename T,
const std::size_t Rows,
const std::size_t Columns >
void some_func(T(& arr)[Rows][Columns])
{
// do stuff
}
// this works. guarenteed
std::vector< std::vector< int > > vvn(10, std::vector<int>(5, 99));
2.
#include <iostream>
int main()
{
using namespace std;
int array[50]= {0};
int (*p)[5]= reinterpret_cast<int (*)[5]> (&array[0]);
for (size_t i= 0; i< 10; ++i)
for(size_t j=0; j<5; ++j)
cout<< p[i][j]<<" ";
cout<< endl;
}
Here p behaves as a 2-dimensional matrix, that is a 10x5 matrix. I think
it is guaranteed to work for the same reason as the first one, that is
we can treat an array (sequence) of integers as various types of integer
arrays.
Anything written in C++ that requires a reinterpret_cast sounds an
alarm here. You can only guess at what the result might be (and
possible test/check the result with typeid).
Hacking is not programming. Respect your types at all costs. Its
directive #1, no exceptions.
Anytime you fool a compiler you are preventing it to help you code.
Basicly, you'll code as if it is a 10x5 matrix and then one day
something will change thats beyond your control.
You'll come back 6 months from now, look at your code, needing to
modify it (ie: add features) and reach for the Asprin tablets (imagine
the client-user of your code trying to figure it all out). An apple is
an apple, if you threat it like an orange then you'll eventually fall
in a hole called undefined behaviour. You will, its a question of
time.
Clients/Customers don't like hacks, and sometimes - that client/
customer ... is you.
.
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 02:43:05 PM |
|
|
On Jan 5, 6:35 pm, Salt_Peter <pj_h...@yahoo.com> wrote:
On Jan 5, 10:02 am, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=3D0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]=3D {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work,
since all arrays are sequences of their elements.
// Are you sure? try...
int array[10][5]=3D {99};
What does that change? You have different initial values
(array[0][0] =3D=3D 99, all other elements =3D=3D 0). But there is
still an array bounds violation in the function, which is
undefined behavior.
// and as far as a function for an array:
template< typename T,
const std::size_t Rows,
const std::size_t Columns >
void some_func(T(& arr)[Rows][Columns])
{
// do stuff
}
// this works. guarenteed
std::vector< std::vector< int > > vvn(10, std::vector<int>(5, 99));
That does something different. It initializes all of the
elements with 99, rather than the first with 99, and all of the
others with 0.
2.
#include <iostream>
int main()
{
using namespace std;
int array[50]=3D {0};
int (*p)[5]=3D reinterpret_cast<int (*)[5]> (&array[0]);
for (size_t i=3D 0; i< 10; ++i)
for(size_t j=3D0; j<5; ++j)
cout<< p[i][j]<<" ";
cout<< endl;
}
Here p behaves as a 2-dimensional matrix, that is a 10x5
matrix. I think it is guaranteed to work for the same reason
as the first one, that is we can treat an array (sequence)
of integers as various types of integer arrays.
Anything written in C++ that requires a reinterpret_cast
sounds an alarm here. You can only guess at what the result
might be (and possible test/check the result with typeid).
That's not quite true---there are a few things you can do with
reinterpret_cast which have defined behavior. But this isn't
one of them. On the other hand, the expressed intent of
reinterpret_cast in the standard is to support type punning, in
so far as reasonable on the underlying architecture, so from a
quality of implementation point of view, I would expect it to
work on most architectures.
Hacking is not programming. Respect your types at all costs.
Its directive #1, no exceptions.
C++ has reinterpret_cast for a reason. I use it, for example,
when implementing things like malloc or garbage collection. In
such cases, it's a necessary evil.
In anything but such low level (architecture dependent)
programming, of course, it's a guaranteed problem, if only for
reasons of readability.
(For the rest, I very much agree with the part I've cut.
Anything involving reinterpret_cast is a hack, and hacks should
be reserved for the cases where they are absolutely necessary.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 02:22:57 PM |
|
|
James Kanze wrote:
int array[10][5]=3D {99};
What does that change? =A0You have different initial values
(array[0][0] =3D=3D 99, all other elements =3D=3D 0). =A0
I think it is not a best idea to trust that all other elements will be
zero. Default constructor for int will be called, and i have some
compilers, which do nothing in the case (a trash from previous memory
users will be found in the array). The same thing does for pointers
(int* for example).
C++ has reinterpret_cast for a reason. =A0I use it, for example,
when implementing things like malloc or garbage collection. =A0In
such cases, it's a necessary evil.
In anything but such low level (architecture dependent)
programming, of course, it's a guaranteed problem, if only for
reasons of readability.
One can make separated namespaces to locate code specific for each
architecture and later to write using for correct one. A general
(suitable for all platforms, but with bad performance) form of the
code often can exist also in own namespace.
Maksim A. Polyanin
old page about some C++ improvements:
http://grizlyk1.narod.ru/cpp_new
.
|
|
|
| User: "Default User" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 03:02:08 PM |
|
|
Grizlyk wrote:
James Kanze wrote:
int array[10][5]= {99};
What does that change? You have different initial values
(array[0][0] == 99, all other elements == 0).
I think it is not a best idea to trust that all other elements will be
zero. Default constructor for int will be called, and i have some
compilers, which do nothing in the case (a trash from previous memory
users will be found in the array). The same thing does for pointers
(int* for example).
That's not correct. If an incomplete initializer is used, all other
elements will be initialized as if it were set to 0. I don't know if
the C++ standard covers it specifically (my copy is at work), as it's
inherited from C. Here's the C99 draft standard on the issue:
[#21] If there are fewer initializers in a brace-enclosed
list than there are elements or members of an aggregate, or
fewer characters in a string literal used to initialize an
array of known size than there are elements in the array,
the remainder of the aggregate shall be initialized
implicitly the same as objects that have static storage
duration.
Brian
.
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 05:06:56 PM |
|
|
Default User wrote:
I think it is not a best idea to trust that all other elements will be
zero. Default constructor for int will be called, and i have some
compilers, which do nothing in the case (a trash from previous memory
users will be found in the array). The same thing does for pointers
(int* for example).
That's not correct. If an incomplete initializer is used, all other
elements will be initialized as if it were set to 0. I don't know if
the C++ standard covers it specifically (my copy is at work), as it's
inherited from C. Here's the C99 draft standard on the issue:
=A0 =A0 =A0 =A0[#21] If there are fewer initializers =A0in =A0a =A0brace-e=
nclosed
=A0 =A0 =A0 =A0list =A0than there are elements or members of an aggregate,=
or
=A0 =A0 =A0 =A0fewer characters in a string literal used to =A0initialize =
=A0an
=A0 =A0 =A0 =A0array =A0of =A0known =A0size than there are elements in the=
array,
=A0 =A0 =A0 =A0the =A0remainder =A0of =A0the =A0aggregate =A0shall =A0 be =
=A0 initialized
=A0 =A0 =A0 =A0implicitly =A0the =A0same =A0as =A0objects =A0that have sta=
tic storage
=A0 =A0 =A0 =A0duration.
Yes, i have been confused with arrays without partitional
initialization, that is not the same.
.
|
|
|
| User: "Default User" |
|
| Title: Re: Validity of pointer conversions |
07 Jan 2008 01:03:52 AM |
|
|
Grizlyk wrote:
Default User wrote:
I think it is not a best idea to trust that all other elements
will be zero.
That's not correct. If an incomplete initializer is used, all other
elements will be initialized as if it were set to 0. I don't know if
the C++ standard covers it specifically (my copy is at work), as
it's inherited from C. Here's the C99 draft standard on the issue:
Yes, i have been confused with arrays without partitional
initialization, that is not the same.
Right. Automatic aggregates without an initializer have indeterminant
values.
Brian
.
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
07 Jan 2008 09:08:55 AM |
|
|
Default User wrote:
Yes, i have been confused with arrays without partitional
initialization, that is not the same.
Right. Automatic aggregates without an initializer
have indeterminant values.
By the way, "the remainder of the aggregate shall be initialized
implicitly the same as objects that have static storage duration."
The clause "the same as objects that have static storage duration" is
not the same as "filled by zero". Let's look like compilers make
static arrays:
char b[100]={1,2};
_b:
.byte 1
.byte 2
.space 98
or
_b label byte
db 1
db 2
db 98 dup(?)
or
_b label byte
db 1
db 2
db 98 dup(0)
in first two cases all depends from asm conventions for ".space" and
"dup(?)" directives. It is possible, that third-part asm will treat
the directives not only as zero-filling.
Maksim A. Polyanin
old page about some C++ improvements:
http://grizlyk1.narod.ru/cpp_new
.
|
|
|
| User: "Default User" |
|
| Title: Re: Validity of pointer conversions |
07 Jan 2008 11:46:39 AM |
|
|
Grizlyk wrote:
Default User wrote:
Yes, i have been confused with arrays without partitional
initialization, that is not the same.
Right. Automatic aggregates without an initializer
have indeterminant values.
By the way, "the remainder of the aggregate shall be initialized
implicitly the same as objects that have static storage duration."
The clause "the same as objects that have static storage duration" is
not the same as "filled by zero".
From the C++ standard:
3.6.2 Initialization of non-local objects [basic.start.init]
1 Objects with static storage duration (3.7.1) shall be
zero-initialized (8.5) before any other initialization takes place.
Zero-initialization and initialization with a constant expression are
collectively called static initialization; all other initialization is
dynamic initialization. Objects of POD types (3.9) with static storage
duration initialized with constant expressions (5.19) shall be
initialized before any dynamic initialization takes place. Objects with
static storage duration defined in namespace scope in the same
translation unit and dynamically initialized shall be initialized in
the order in which their definition appears in the translation unit.
[Note: 8.5.1 describes the order in which aggregate members are
initialized. The initialization of local static objects is described in
6.7. ]
The particular example was for ints. So the zero-initialization is the
only one that will take place.
Let's look like compilers make
static arrays:
char b[100]={1,2};
_b:
.byte 1
.byte 2
.space 98
or
_b label byte
db 1
db 2
db 98 dup(?)
or
_b label byte
db 1
db 2
db 98 dup(0)
in first two cases all depends from asm conventions for ".space" and
"dup(?)" directives. It is possible, that third-part asm will treat
the directives not only as zero-filling.
I have no idea what you are going on about. The standard requires
zero-initialization. If you think you see somethine else then either:
1. You're wrong.
2. You have a non-conforming implementation.
Now that I'm at work, I have access to the C++ standard. Here's what it
says:
8.5.1 Aggregates
If there are fewer initializers in the list than there are members in
the aggregate, then each member not explicitly initialized shall be
value-initialized (8.5). [Example:
struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };
initializes ss.a with 1, ss.b with "asdf", and ss.c with the value of
an expression of the form int(), that is, 0. ]
In the example from the standard, the aggregate is a struct, but same
principle applies to other types, like arrays. Generally, all the
elements in not explicitly initalized will be set to the appropriate
"0" for the type. In the case we had, it was ints, so 0.
Brian
.
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
07 Jan 2008 01:52:46 PM |
|
|
Default User wrote:
The clause "the same as objects that have static storage
duration" is not the same as "filled by zero".
From the C++ standard:
1 Objects with static storage duration (3.7.1) shall be
zero-initialized (8.5) before any other initialization takes place.
This is quite clear now:
1. Default constructor for static POD type makes zero-initialized
value.
2. Default constructor for not static POD type can do nothing.
There are fewer initializers of an aggregate or an array of known
size, remainder of the aggregate or the array shall be initialized
implicitly the same as objects that have static storage duration.
3. So default constructor for not static POD type for remainder of the
aggregate or the array makes zero-initialized value.
4. For non-POD values default ctor will be called always, even for
automaic aggregates or arrays.
Let's look like compilers make static arrays:
I have no idea what you are going on about.
It is easy to understand
char =A0 =A0 =A0 b[100]=3D{1,2};
is C++ source code with static array b.
_b:
=A0 =A0.byte =A0 1
=A0 =A0.byte =A0 2
=A0 =A0.space 98
or
_b label =A0 byte
=A0 =A0db =A0 =A0 =A01
=A0 =A0db =A0 =A0 =A02
=A0 =A0db =A0 =A0 =A098 =A0 =A0 =A0dup(?)
is oputput compiled from C++ into assembler for a concrete CPU.
all depends from asm conventions for ".space" and
"dup(?)" directives.
It is means, that if assembler can emit for directive "dup(?)"
executable with any random values excluding 0 (there are the
assemblers in the world), the C++ compiler became non-standard (will
have a non-conforming implementation).
I think, that C++ programmer must not take in account the low-level
details of C++ compiler implementation and trust, that considered
arrays will be zero initialized for "conforming implementation".
Maksim A. Polyanin
old page about some C++ improvements:
http://grizlyk1.narod.ru/cpp_new
.
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
08 Jan 2008 04:30:00 AM |
|
|
On Jan 7, 8:52 pm, Grizlyk <grizl...@yandex.ru> wrote:
Default User wrote:
The clause "the same as objects that have static storage
duration" is not the same as "filled by zero".
From the C++ standard:
1 Objects with static storage duration (3.7.1) shall be
zero-initialized (8.5) before any other initialization takes place.
This is quite clear now:
1. Default constructor for static POD type makes zero-initialized
value.
2. Default constructor for not static POD type can do nothing.
Only class types have constructors, and the default constructor
of a POD class does nothing. Ever.
Default initialization of a non-POD class type calls the default
constructor. Default initialization of an array
default-initializes each element. Default initialization of any
other type is zero initialization.
If a definition of an aggregate has an initializer list,
elements for which no initializer is given are default
initialized---for a POD type, that means zero initialized.
If a definition of POD has no initializer, then it is not
initialized. All objects (even those with user defined
constructors) with static lifetime are zero initialized before
program start-up, however.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
| User: "Ioannis Vranos" |
|
| Title: Re: Validity of pointer conversions |
08 Jan 2008 07:18:07 AM |
|
|
James Kanze wrote:
All objects (even those with user defined
constructors) with static lifetime are zero initialized before
program start-up, however.
I assune you mean they are initialised in the style
T obj= T();
for these objects, since those with user defined constructors may be not
possible to initialise with 0.
.
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
09 Jan 2008 02:44:00 AM |
|
|
On Jan 8, 2:18 pm, Ioannis Vranos <j...@no.spam> wrote:
James Kanze wrote:
All objects (even those with user defined constructors) with
static lifetime are zero initialized before program
start-up, however.
I assune you mean they are initialised in the style
T obj=3D T();
for these objects, since those with user defined constructors
may be not possible to initialise with 0.
No, since if T has a non-trivial constructor, it has dynamic
initialization. Formally, initialization of variables with
static lifetime takes place in three steps:
1. Zero initialization. This takes place before any other
initialization, and affects *all* objects, regardless of
type.
Zero initialization of an arithmetic type, and enum or a
pointer is the same as initializing it with 0, converted to
the corresponding type. (Note that it does *not*
necessarily mean all bits zero, although this is the case on
most frequent architectectures.)
Zero initialization of a reference means nothing.
Zero initialization of a class type or an array means zero
initialization of each of a its elements. This recurses
down until you get to something in one of the above cases.
2. Static initialization. This concerns arithmetic types,
pointers and enums initialized with a constant expression,
and class types with trivial constructors initialized with
the aggregate initialization syntax in which all of the
initializers are constant expressions. It also concerns
arrays all of whose elements can be statically initialized.
3. Dynamic initialization. All of the rest, this involves
execution of code (which in turn can mean order of
initialization issues).
In practice, since static initialization doesn't involve
execution of code, there's no way a program can tell whether
zero initialization took place or not. The first line of code
you wrote only gets executed after static initialization is
finished. On the other hand, it is quite possible to observe
the "double" initialization when dynamic initialization is
involved:
#include <iostream>
extern int f() ;
struct Toto
{
int a ;
Toto() ;
} ;
Toto t ;
int b =3D f() ;
int f()
{
return 42 ;
}
Toto::Toto()
: a( b )
{
}
int
main()
{
std::cout << t.a << std::endl ;
return 0 ;
}
This program is guaranteed to output 0.
More generally, I use this to ensure correct initialization of a
singleton during static initialization (and thus, normally,
before threading starts).
(Technically, I'm not sure, but I think an implementation is
allowed to scribble over the memory before calling a
constructor, but practically, none do, and in some very
exceptional cases, I've also exploited this to allow objects to
be used before being constructed.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
08 Jan 2008 09:22:05 AM |
|
|
James Kanze wrote:
Only class types have constructors, and the default constructor
of a POD class does nothing. =A0Ever.
The fact, that "default constructor of a POD class does nothing" can
be a point of confusion. Consider:
template<typename T>
void foo()
{
T a;
T b=3DT();
// ...
}
Is "T()" default constructor or not? "T()" is looks like default
constructor rather than initializer. But for
template<>
void foo<int>();
"b" will be initialized, but "a" will not.
"T b=3D0" looks like initializer more than "T b=3DT()".
Maksim A. Polyanin
old page about some C++ improvements:
http://grizlyk1.narod.ru/cpp_new
.
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
09 Jan 2008 03:03:00 AM |
|
|
On Jan 8, 4:22 pm, Grizlyk <grizl...@yandex.ru> wrote:
James Kanze wrote:
Only class types have constructors, and the default
constructor of a POD class does nothing. Ever.
The fact, that "default constructor of a POD class does
nothing" can be a point of confusion.
Yes, until you realize that "initialization" can be more than
just calling the constructor.
Consider:
template<typename T>
void foo()
{
T a;
T b=3DT();
// ...
}
Is "T()" default constructor or not? "T()" is looks like
default constructor rather than initializer.
Good point. T() is a special case:-). More generally, of
course, if T is the name of a type, T(...) is a special case: it
constructs an object, according to a certain set of rules. If
the object is of class type, those rules will result in the
constructor being called (at the end). The expression itself,
of course, will do more than just call the constructor (and is
valid for non-class types which do not have constructors).
(Formally, T() is an "explicit type conversion (functional
notation)". Although I can't quite figure out what type is
being converted in cases like T(1,2,3)---"int, int, int" is not
a type in C++. I prefer to think of it as explicit creation of
an object. The exact words you use really don't matter, though,
as long as you realize that it is *not* just calling a
constructor.)
I might add the exact wording concerning this case has changed;
the original standard didn't correctly express exactly what was
wanted.
But for
template<>
void foo<int>();
"b" will be initialized, but "a" will not.
"T b=3D0" looks like initializer more than "T b=3DT()".
Both are copy initialization, with exactly the same semantics.
In one case, what you copy is 0, in the other T(). The key here
is the sentence "An object whose initializer is an empty set of
parentheses, i.e., (), shall be value-initialized." (From the
latest draft---the current version of the standard says
"default-initialized". And the distinction between default
initialization and value-initialization was introduced to cover
some corner cases which weren't handled as intended in the
current standard.)
In practice, of course, all of these rules are overly
complicated, and only really of interest if you're writing a
compiler. For most people, it's sufficient to remember that 1)
static objects are zero initialized before program start up, and
2) initializing with an empty set of parentheses has more or
less the same effect as causing the initialization to be the
same as if the variable were static. I can construct cases
where 2 isn't true, but I can't imagine them being relevant to
any real code (and certainly not to any well written code).
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
|
|
|
|
|
|
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
08 Jan 2008 04:20:33 AM |
|
|
On Jan 6, 9:22 pm, Grizlyk <grizl...@yandex.ru> wrote:
James Kanze wrote:
int array[10][5]=3D {99};
What does that change? You have different initial values
(array[0][0] =3D=3D 99, all other elements =3D=3D 0).
I think it is not a best idea to trust that all other elements
will be zero.
The standard says that all other elements will be zero. Are you
claiming that you've had problems with compiler errors in this
regard?
Default constructor for int will be called, and i have some
compilers, which do nothing in the case (a trash from previous
memory users will be found in the array). The same thing does
for pointers (int* for example).
Which compilers? The standard is very explicit: "if there are
fewer initializers in the list than there are members in the
aggregate, then each member not explicitly initialized shall be
default-initialized." (That's from C++98. The exact wording
has changed some, but in no way which affects PODs.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
08 Jan 2008 09:01:55 AM |
|
|
James Kanze wrote:
The standard says that all other elements will be zero. =A0Are you
claiming that you've had problems with compiler errors in this
regard?
Default constructor for int will be called, and i have some
compilers, which do nothing in the case (a trash from previous
memory users will be found in the array). The same thing does
for pointers (int* for example).
Which compilers? =A0
Not shure about meaning of the word "claiming", but i had a
possibilities to get the memory garbage. I have posted in the thread
above description for the thrash appearance.
The error is outside of C++ scope - within interface between
particular C++ copmiler for concrete CPU and concrete native CPU
assembler. Is it interesting here?
Gcc v4.1 for x86 can create asm output with ".space". Free bcc32 v5
for x86 can create asm output with "dup(?)" (note, earlier versions of
the compiler emit "dup(0)" explicitly).
For ".space" it is required that 0 is by default for skipped values,
but for "dup(?)" is not.
But in both cases some assemblers can create for dup(?) and ".space"
any random values. They use "right" to do nothing for all that was not
explicitly defined, probably, in a hope to place all the undefined
values into BSS-like segments of executable :).
Maksim A. Polyanin
old page about some C++ improvements:
http://grizlyk1.narod.ru/cpp_new
.
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
09 Jan 2008 03:16:42 AM |
|
|
On Jan 8, 4:01 pm, Grizlyk <grizl...@yandex.ru> wrote:
James Kanze wrote:
The standard says that all other elements will be zero. Are you
claiming that you've had problems with compiler errors in this
regard?
Default constructor for int will be called, and i have some
compilers, which do nothing in the case (a trash from previous
memory users will be found in the array). The same thing does
for pointers (int* for example).
Which compilers?
Not shure about meaning of the word "claiming", but i had a
possibilities to get the memory garbage.
Again: with which compiler? I've never seen a compiler which
got this wrong.
I have posted in the thread above description for the thrash
appearance.
The error is outside of C++ scope - within interface between
particular C++ copmiler for concrete CPU and concrete native CPU
assembler. Is it interesting here?
I'm not sure what you're point is. The fact that you can write
code in assembler which results in trash values has nothing to
do with C++. *If* the C++ compiler goes through an assembler
phase (very few do today), then it is the responsibility of the
compiler to generate assembler code which has the required
semantics.
Gcc v4.1 for x86 can create asm output with ".space".
Gcc 4.1 doesn't generate assembler; it generates object code
directly. *If* you specify the -S option, it will generate
assembler, but obviously, that assembler code "supposes" a
specific assembler, on a specific machine, which does whatever
gcc supposes it does correctly. (And of course, gcc doesn't
really document what assumptions it makes concerning the
assembler. If you generate assembler code, and then try to use
it, it's up to you to ensure that everything is correct and what
you want. As a quality of implementation issue, gcc tries to
get the assembler "right", but it's really irrelevant to
C++.)
Free bcc32 v5 for x86 can create asm output with "dup(?)"
(note, earlier versions of the compiler emit "dup(0)"
explicitly).
For ".space" it is required that 0 is by default for skipped values,
but for "dup(?)" is not.
Presumably, free bcc323 supposes a compiler (or an environment)
where dup(?) does result in 0 initialization. (Just a guess,
but I suspect that dup(0) will result in the data being laid out
in the data segment on disk, where as dup(?) will put the data
in the bss segment, with no initialization on disk---this wasn't
how the original Intel assembler worked, but it seems reasonable
for a Windows or Unix machine. Under Windows or Unix, the OS
initializes the bss to all bits 0, for security reasons, if
nothing else. And under Windows and Unix, it just happens that
zero initialization always corresponds to all bits 0.)
But in both cases some assemblers can create for dup(?) and
".space" any random values. They use "right" to do nothing for
all that was not explicitly defined, probably, in a hope to
place all the undefined values into BSS-like segments of
executable :).
And the OS's I use guarantee that all BSS-like segments are zero
initialized. (Any multi-user OS must guarantee that all memory
obtained from the system is initialized somehow---both Windows
and Unix guarantee 0.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
09 Jan 2008 07:26:28 AM |
|
|
James Kanze wrote:
But in both cases some assemblers can create for dup(?) and
".space" any random values. They use "right" to do nothing for
all that was not explicitly defined, probably, in a hope to
place all the undefined values into BSS-like segments of
executable :).
And the OS's I use guarantee that all BSS-like segments are zero
initialized. =A0(Any multi-user OS must guarantee that all memory
obtained from the system is initialized somehow---both Windows
and Unix guarantee 0.)
I agree, that all (hidden for C++ standard) parts of C++ compiler must
work correctly and interact together perfectly. By standard the
considered C++ data must be initialized by zero.
But as you touch the scope outside of C++ standard, i can say, that C+
+ compiler can be used as front-end compiler, to produce intermediate
sources, and the sources can be used later in free manner to make
(with the help of particular codegenerators) concrete executable for
concrete environment.
It is very interesting for me, i can not prove my opinion, but never
heard befor, that any OS or executable environment take for itself the
job to do any certain things with the memory, that has been allocated
for process from OS. The stub-like program _can_ do any initialization
itself for the allocated memory, but there are no any garantees, that
any OS or stub _must_ do something, just because random filled memory
is initialized somehow also.
Maksim A. Polyanin
old page about some C++ improvements:
http://grizlyk1.narod.ru/cpp_new
.
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
09 Jan 2008 02:31:44 PM |
|
|
On Jan 9, 2:26 pm, Grizlyk <grizl...@yandex.ru> wrote:
[...]
It is very interesting for me, i can not prove my opinion, but
never heard befor, that any OS or executable environment take
for itself the job to do any certain things with the memory,
that has been allocated for process from OS. The stub-like
program _can_ do any initialization itself for the allocated
memory, but there are no any garantees, that any OS or stub
_must_ do something, just because random filled memory is
initialized somehow also.
Well, an OS could fill the memory with random values, rather
than 0, I suppose. But just leaving whatever happened to be
there would be a serious security leak. (Suppose that the last
process to use that memory just happened to store your password
in it.)
Unix always initialized the "bss" segment to 0, from the day 1.
I don't think MS-DOS pre-initialized anything, but I'm pretty
sure Windows does.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
|
|
|
|
|
|
|
| User: "Ioannis Vranos" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 02:47:26 PM |
|
|
Salt_Peter wrote:
On Jan 5, 10:02 am, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work, since all
arrays are sequences of their elements.
// Are you sure? try...
int array[10][5]= {99};
OK, it prints:
[john@localhost src]$ ./foobar-cpp
99 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0
as expected. When initialising a built-in array with initial values, the
rest members of the array that are not explicitly assigned with an
initial value, are initialised with 0.
// and as far as a function for an array:
template< typename T,
const std::size_t Rows,
const std::size_t Columns >
void some_func(T(& arr)[Rows][Columns])
{
// do stuff
}
// this works. guarenteed
std::vector< std::vector< int > > vvn(10, std::vector<int>(5, 99));
I am not looking for ways to do it. I am just asking if these specific
uses are guaranteed to work as expected.
Anything written in C++ that requires a reinterpret_cast sounds an
alarm here. You can only guess at what the result might be (and
possible test/check the result with typeid).
Hacking is not programming. Respect your types at all costs. Its
directive #1, no exceptions.
Anytime you fool a compiler you are preventing it to help you code.
Basicly, you'll code as if it is a 10x5 matrix and then one day
something will change thats beyond your control.
You'll come back 6 months from now, look at your code, needing to
modify it (ie: add features) and reach for the Asprin tablets (imagine
the client-user of your code trying to figure it all out). An apple is
an apple, if you threat it like an orange then you'll eventually fall
in a hole called undefined behaviour. You will, its a question of
time.
Clients/Customers don't like hacks, and sometimes - that client/
customer ... is you.
I am asking if it is a *valid* low-level behaviour, and not an undefined
behaviour. We could use
int (*p)[5]= static_cast<int (*)[5]> (static_cast<void *>(&array[0]));
instead of the reinterpret_cast instead.
My question (actually what I think I know and I want others to verify)
is, a built in array of type T, is a sequence of its members of type T,
and thus we can treat it as arrays of various forms.
Consider another example:
#include <iostream>
#include <cstdlib>
int main()
{
using namespace std;
const char *pc= "This is a test.";
// 100% guaranteed to work
const char *p1= pc;
while(*p1)
cout<< *p1++;
cout<< endl;
// 100% guaranteed to work
const char (*p2)[16]= reinterpret_cast<const char (*)[16]>(pc);
for(size_t j= 0; j<sizeof(*p2)/sizeof(**p2); ++j)
cout<< p2[0][j];
cout<< endl;
// ==> Here is my question. Is it 100% guaranteed to work? AFAIK yes.
const char (*p3)[8]= reinterpret_cast<const char (*)[8]>(pc);
for(size_t i= 0; i<2; ++i)
for(size_t j= 0; j<sizeof(*p3)/sizeof(**p3); ++j)
cout<< p3[i][j];
cout<< endl;
}
.
|
|
|
| User: "Grizlyk" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 04:00:33 PM |
|
|
Ioannis Vranos wrote:
I am asking if it is a *valid* low-level behaviour,
and not an undefined behaviour. We could use
int (*p)[5]= static_cast<int (*)[5]>
(static_cast<void *>(&array[0]));
instead of the reinterpret_cast.
My question (actually what I think I know and I want
others to verify) is, a built in array of type T,
is a sequence of its members of type T,
and thus we can treat it as arrays of various forms.
Standard requires for type "char", that hardware memory is always
logically (for C++) continuous (without holes) area. Each "char" has
own uniq address (value of "char" pointer) of memory and the addresses
are ordered as ordinary continuous numbers, like 1,2,3, etc.
As i have understood, the "char" addresses have ability to be hidden
translated into specific hardware adresses. I am not shure about
standard limits of the hidden translation, but hardware memory can be
developed: as banks (plans) of memory, as memory with holes, etc.
For example: "char" pointer with numeric value "1" could be explicitly
translated by compiler into hardware addres with other numeric value -
"23". I am not shure about return value of
reinterpret_cast<int>(char*) in the case: "1" or "23", but for other
cases the value of "char" pointer is "1".
C-style array is logically (for programmer) continuous (without holes)
area. I do not know does standard requre the condition, but do not see
compilers with other one.
In accordance with rules for "char" pointer, listed above and in
accordance with the fact, that array[1] means pointer+1, array[2]
means pointer+2 etc array of char is logically (for C++ and
programmer) continuous (without holes) area.
So the area of array of "char" can be safely divided for any logical
dimensions inside total area size, because access for each member is
independed from dimensions: a[y][x] is *(&a+y*x_size+x). I do not know
why C++ does not allow static_cast for the conversions.
For other types, with size greater than "char", there is alignment.
But once aligned each element of array could be accessible via the
initial aligned pointer - all similar to type "char". "Could" because
i do not know does stadard require the same conditions (to be
continuous area of memory) for any type and its pointer as for "char"
and "char" pointer or does not.
I think dimensions of C-style array are logical and your always have a
solid array of y_size*x_size size.
Maksim A. Polyanin
old page about some C++ improvements:
http://grizlyk1.narod.ru/cpp_new
.
|
|
|
|
| User: "Salt_Peter" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 05:18:03 AM |
|
|
On Jan 5, 3:47 pm, Ioannis Vranos <j...@no.spam> wrote:
Salt_Peter wrote:
On Jan 5, 10:02 am, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work, since all
arrays are sequences of their elements.
// Are you sure? try...
int array[10][5]= {99};
OK, it prints:
[john@localhost src]$ ./foobar-cpp
99 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0
Uh, no it doesn't
99, 0, 0, 0, 0,
99, 0, 0, 0, 0,
-8 more times-
as expected. When initialising a built-in array with initial values, the
rest members of the array that are not explicitly assigned with an
initial value, are initialised with 0.
Indeed, except you have mistaken an array with a 'sequence' of arrays.
There is a critical distinction to be made.
// and as far as a function for an array:
template< typename T,
const std::size_t Rows,
const std::size_t Columns >
void some_func(T(& arr)[Rows][Columns])
{
// do stuff
}
// this works. guarenteed
std::vector< std::vector< int > > vvn(10, std::vector<int>(5, 99));
I am not looking for ways to do it. I am just asking if these specific
uses are guaranteed to work as expected.
Anything written in C++ that requires a reinterpret_cast sounds an
alarm here. You can only guess at what the result might be (and
possible test/check the result with typeid).
Hacking is not programming. Respect your types at all costs. Its
directive #1, no exceptions.
Anytime you fool a compiler you are preventing it to help you code.
Basicly, you'll code as if it is a 10x5 matrix and then one day
something will change thats beyond your control.
You'll come back 6 months from now, look at your code, needing to
modify it (ie: add features) and reach for the Asprin tablets (imagine
the client-user of your code trying to figure it all out). An apple is
an apple, if you threat it like an orange then you'll eventually fall
in a hole called undefined behaviour. You will, its a question of
time.
Clients/Customers don't like hacks, and sometimes - that client/
customer ... is you.
I am asking if it is a *valid* low-level behaviour, and not an undefined
behaviour. We could use
int (*p)[5]= static_cast<int (*)[5]> (static_cast<void *>(&array[0]));
instead of the reinterpret_cast instead.
My question (actually what I think I know and I want others to verify)
is, a built in array of type T, is a sequence of its members of type T,
and thus we can treat it as arrays of various forms.
Here is a typical story about Mr Hacker and Dr Programmer.
Its fictitious although you'ld be surprised how often it happens.
Customer approaches Mr Hacker, he requires a type X to be streameable
to file, a socket and to and from various interfaces.
struct X
{
...
};
Type X is a mix of characters, integers, floats and a few other
members, nothing special. Mr Hacker proceeds to reinterpret_cast an
array of X elements into an array of some primitive type (char, Byte)
and streams the data (including padding!) and completes the job in
about 16 hours. Mr Hacker pridefully sends the project back at which
point the Client announces he's got to stream 2 more types as well. Mr
Hacker's jaw drops, client looks at code, gives Mr Hacker ***** for
streaming the padding. Mr Hacker needs 3 days to get that job done.
Client now wants a couple more classes to be streameable (and he needs
them NOW). Client sees little benefit to continue the contract.
Client goes to see Dr Programmer. Dr Programmer completes the project
in about 16 hours too. Client notices that no padding is being
transferred, nice. Client needs 15 new classes to use the system,
however. Mr Programmer looks at one class - derives it from type X,
writes an insertion operator and an extraction operator for X,
modifies the interface as the compiler complains about the required
pure-virtual functions missing.
Client asks when will the project be ready then?
Mr Programmer says: "sir, you don't need to change a single statement
in our code to make it work with your new class, as long as you derive
from this abstract class".
Mr Programmer: "in fact, the code is set up in such a way that the
compilation errors will tell you whats missing, if anything." (thats
what happens when you treat an apple like an apple)
Client goes back to office, takes out another one of his 15 derived
types, looks at Mr Programmer's modifications to X's derivative ...
and smiles: "wow, i can extend this code without calling Mr
Programmer, and i can do it effortlessly because the code is crystal
clear".
In your opinion - who gets the next contract opportunity?
Consider another example:
#include <iostream>
#include <cstdlib>
int main()
{
using namespace std;
const char *pc= "This is a test.";
// 100% guaranteed to work
const char *p1= pc;
while(*p1)
cout<< *p1++;
cout<< endl;
// 100% guaranteed to work
const char (*p2)[16]= reinterpret_cast<const char (*)[16]>(pc);
for(size_t j= 0; j<sizeof(*p2)/sizeof(**p2); ++j)
cout<< p2[0][j];
cout<< endl;
// ==> Here is my question. Is it 100% guaranteed to work? AFAIK yes.
const char (*p3)[8]= reinterpret_cast<const char (*)[8]>(pc);
for(size_t i= 0; i<2; ++i)
for(size_t j= 0; j<sizeof(*p3)/sizeof(**p3); ++j)
cout<< p3[i][j];
cout<< endl;
}
.
|
|
|
| User: "Ioannis Vranos" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 05:32:30 AM |
|
|
Salt_Peter wrote:
// Are you sure? try...
int array[10][5]= {99};
OK, it prints:
[john@localhost src]$ ./foobar-cpp
99 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0
Uh, no it doesn't
99, 0, 0, 0, 0,
99, 0, 0, 0, 0,
-8 more times-
Can you provide the exact code you are using, along with its output?
.
|
|
|
|
|
|
|
| User: "James Kanze" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 02:34:38 PM |
|
|
On Jan 5, 4:02 pm, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=3D0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]=3D {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work,
since all arrays are sequences of their elements.
And? I don't see any relationship between what you just said
and any guarantee of working. You have an array bounds
violation, which is undeefined behavior. And there have been
(and maybe still are) implementations which detect it, and
treat it as an error condition.
2.
#include <iostream>
int main()
{
using namespace std;
int array[50]=3D {0};
int (*p)[5]=3D reinterpret_cast<int (*)[5]> (&array[0]);
for (size_t i=3D 0; i< 10; ++i)
for(size_t j=3D0; j<5; ++j)
cout<< p[i][j]<<" ";
cout<< endl;
}
Here p behaves as a 2-dimensional matrix, that is a 10x5
matrix.
Almost nothing involving reinterpret_cast is guaranteed to work.
About the only thing that the standard guarantees is that if you
cast the value back to its original type, and you haven't
violated any alignment restrictions in the intermediate types,
the value will compare equal to the original value (and thus,
designate the same object designated by the original pointer).
=46rom a quality of implementation point of view: the standard
does say that the conversion is expected to be "unsurprising"
for someone familiar with the addressing architecture of the
processor, so I would expect this to work on most
implementations.
I think it is guaranteed to work for the same reason
as the first one,
It is totally unrelated to the first. reinterpret_cast is quite
different from other conversions.
that is we can treat an array (sequence) of integers as
various types of integer arrays.
If by that you mean that you can play games with the dimensions,
as long as the total number of elements is unchanged, that is
simply false.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34
.
|
|
|
| User: "Ioannis Vranos" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 03:02:48 PM |
|
|
James Kanze wrote:
On Jan 5, 4:02 pm, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work,
since all arrays are sequences of their elements.
And? I don't see any relationship between what you just said
and any guarantee of working. You have an array bounds
violation, which is undeefined behavior. And there have been
(and maybe still are) implementations which detect it, and
treat it as an error condition.
What exact array bounds violation is there in the code above?
"int array[10][5];" is a sequence of 50 integers.
2.
#include <iostream>
int main()
{
using namespace std;
int array[50]= {0};
int (*p)[5]= reinterpret_cast<int (*)[5]> (&array[0]);
for (size_t i= 0; i< 10; ++i)
for(size_t j=0; j<5; ++j)
cout<< p[i][j]<<" ";
cout<< endl;
}
Here p behaves as a 2-dimensional matrix, that is a 10x5
matrix.
Almost nothing involving reinterpret_cast is guaranteed to work.
OK, consider int (*p)[5]= static_cast<int (*)[5]> (static_cast<void
*>(&array[0])); instead.
If by that you mean that you can play games with the dimensions,
as long as the total number of elements is unchanged, that is
simply false.
Why? In all cases we have the same sequence of ints, that is
int array[50], int array[10][5], int array[5][10] are all implemented as
the same sequence of 50 ints. If they are not implemented in the same
way, where do they differ?
Thanks.
.
|
|
|
| User: "" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 04:29:54 PM |
|
|
Ioannis Vranos wrote:
James Kanze wrote:
On Jan 5, 4:02 pm, Ioannis Vranos <j...@no.spam> wrote:
Are the following codes guaranteed to work always?
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
The above prints 50 zeros. I think it is guaranteed to work,
since all arrays are sequences of their elements.
And? I don't see any relationship between what you just said
and any guarantee of working. You have an array bounds
violation, which is undeefined behavior. And there have been
(and maybe still are) implementations which detect it, and
treat it as an error condition.
What exact array bounds violation is there in the code above?
"int array[10][5];" is a sequence of 50 integers.
No. It is a array of 10 arrays of 5 int. It so happens to be guaranteed that
the total of 50 int are arranged contiguously in memory, but that does not
magically turn int array[10][5] into an array of 50 int. Consequently, an
expression like
array[7][6]
is an out-of-bounds access to the 6th element (which does not exist) of the
7th array of 5 int.
2.
#include <iostream>
int main()
{
using namespace std;
int array[50]= {0};
int (*p)[5]= reinterpret_cast<int (*)[5]> (&array[0]);
for (size_t i= 0; i< 10; ++i)
for(size_t j=0; j<5; ++j)
cout<< p[i][j]<<" ";
cout<< endl;
}
Here p behaves as a 2-dimensional matrix, that is a 10x5
matrix.
Almost nothing involving reinterpret_cast is guaranteed to work.
OK, consider int (*p)[5]= static_cast<int (*)[5]> (static_cast<void
*>(&array[0])); instead.
If by that you mean that you can play games with the dimensions,
as long as the total number of elements is unchanged, that is
simply false.
Why? In all cases we have the same sequence of ints, that is
int array[50], int array[10][5], int array[5][10] are all implemented as
the same sequence of 50 ints. If they are not implemented in the same
way, where do they differ?
They differ in type. This information is known to the compiler and the
compiler is free to detect that
array[7][6]
is an out-of-bounds access. You will not find that casting pointer makes any
guarantees that type-derived bounds can be moved via casting.
Best
Kai-Uwe Bux
.
|
|
|
| User: "Ioannis Vranos" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 06:28:45 PM |
|
|
wrote:
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
What exact array bounds violation is there in the code above?
"int array[10][5];" is a sequence of 50 integers.
No. It is a array of 10 arrays of 5 int. It so happens to be guaranteed that
the total of 50 int are arranged contiguously in memory, but that does not
magically turn int array[10][5] into an array of 50 int. Consequently, an
expression like
array[7][6]
is an out-of-bounds access to the 6th element (which does not exist) of the
7th array of 5 int.
Yes I can understand that having defined an array with the name array as:
int array[10][5];
with array[7][6] we are accessing out of the array (and the
implementation sequence).
However in the above code I am using an int pointer to output all
members of the array (50 in total) in a 1-dimension array fashion. I do
not point to any element after the one past the end, or to any before
the first one. So I am not violating the boundaries of the sequence.
int array[50], int array[10][5], int array[5][10] are all implemented as
the same sequence of 50 ints. If they are not implemented in the same
way, where do they differ?
They differ in type.
Yes I know they differ in type. Also it is 100% guaranteed to consider
any of the array examples above as an array of unsigned char.
This information is known to the compiler and the
compiler is free to detect that
array[7][6]
is an out-of-bounds access. You will not find that casting pointer makes any
guarantees that type-derived bounds can be moved via casting.
I do not know to which "array" definition you are referring with that.
Can you provide the definition along with your out-of-bounds access
example, and mention where I access out-of-bounds?
Thanks.
.
|
|
|
| User: "" |
|
| Title: Re: Validity of pointer conversions |
05 Jan 2008 09:43:20 PM |
|
|
Ioannis Vranos wrote:
jkherciueh@gmx.net wrote:
1.
#include <iostream>
inline void some_func(int *p, const std::size_t SIZE)
{
using namespace std;
for(size_t i=0; i<SIZE; ++i)
cout<< p[i]<< " ";
}
int main()
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
std::cout<< std::endl;
}
What exact array bounds violation is there in the code above?
"int array[10][5];" is a sequence of 50 integers.
No. It is a array of 10 arrays of 5 int. It so happens to be guaranteed
that the total of 50 int are arranged contiguously in memory, but that
does not magically turn int array[10][5] into an array of 50 int.
Consequently, an expression like
array[7][6]
is an out-of-bounds access to the 6th element (which does not exist) of
the 7th array of 5 int.
Yes I can understand that having defined an array with the name array as:
int array[10][5];
with array[7][6] we are accessing out of the array (and the
implementation sequence).
Out of the 7th array, yes; but not out of the implementation sequence:
7*5+6 = 41 < 10*5 = 50
However in the above code I am using an int pointer to output all
members of the array (50 in total) in a 1-dimension array fashion. I do
not point to any element after the one past the end, or to any before
the first one. So I am not violating the boundaries of the sequence.
You _assume_ that 50 int that lie contiguously in memory can be treated as
an array and that pointer-arithmetic can be used to access any of them
given a pointer to the first. That hypothesis, however, is not waranted by
the standard. To get rid of all the function call ramifications in your
example, consider the following:
int array [10][5] = {0};
int* p = &array[0][0]; // line 2
std::cout << p[5] << '\n'; // line 3
This compares to your code since &array[0][0] is what array[0] decays to
when passed as a parameter to some_func.
The question is whether the third line has undefined behavior. Consider the
following hypothetical implementation of pointers: a pointer is a tripple
of three addresses, the first to the pointee and the other two specifying a
valid range for pointer arithmetic. Similarly, every array (static or
dynamic) has its bounds stored somewhere. When an array decays to a
pointer, these bounds are used to deduce the range for the pointer.
Whenever pointer-arithmetic yields a pointer outside the valid range,
dereferencing triggers a segfault. Such an implementation is not ruled out
by any provisions of the standard that I know of.
Note that in line 2, the compiler has static type information about the rhs.
The rhs is a pointer to the first element in an array of 5. Thus, in the
described implementation, p will be given a range of size 5 and line 3 is
an out-of-bounds access since it dereferences the past-end position of the
5 int sequence array[0] in a way that is obtained through pointer
arithmetic from a pointer into the 5 int sequence array[0].
If you think that a range-checking implementation is not standard
conforming, please provide some language from the standard supporting your
point of view. Note that the guarantee of arrays being contiguous is met by
such an implementation. What such an implementation prevents is just the
reinterpretation of array sizes through pointer casting and pointer
arithmetic.
[snip]
Best
Kai-Uwe Bux
.
|
|
|
| User: "Ioannis Vranos" |
|
| Title: Re: Validity of pointer conversions |
06 Jan 2008 05:28:51 AM |
|
|
wrote:
Yes I can understand that having defined an array with the name array as:
int array[10][5];
with array[7][6] we are accessing out of the array (and the
implementation sequence).
Out of the 7th array, yes; but not out of the implementation sequence:
7*5+6 = 41 < 10*5 = 50
Yes, you are right, it is out of bounds for the array since it is
defined as int array[10][5]; but my uses are not out of the bounds of
the declared pointer types.
Also consider this. We can initialise the array in both ways:
int array[10][5]=
{{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1}};
and
int array[10][5]=
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
(as a sequence).
You _assume_ that 50 int that lie contiguously in memory can be treated as
an array and that pointer-arithmetic can be used to access any of them
given a pointer to the first. That hypothesis, however, is not waranted by
the standard. To get rid of all the function call ramifications in your
example, consider the following:
int array [10][5] = {0};
int* p = &array[0][0]; // line 2
std::cout << p[5] << '\n'; // line 3
This compares to your code since &array[0][0] is what array[0] decays to
when passed as a parameter to some_func.
The question is whether the third line has undefined behavior. Consider the
following hypothetical implementation of pointers: a pointer is a tripple
of three addresses, the first to the pointee and the other two specifying a
valid range for pointer arithmetic. Similarly, every array (static or
dynamic) has its bounds stored somewhere. When an array decays to a
pointer, these bounds are used to deduce the range for the pointer.
Whenever pointer-arithmetic yields a pointer outside the valid range,
dereferencing triggers a segfault. Such an implementation is not ruled out
by any provisions of the standard that I know of.
Note that in line 2, the compiler has static type information about the rhs.
The rhs is a pointer to the first element in an array of 5. Thus, in the
described implementation, p will be given a range of size 5 and line 3 is
an out-of-bounds access since it dereferences the past-end position of the
5 int sequence array[0] in a way that is obtained through pointer
arithmetic from a pointer into the 5 int sequence array[0].
If you think that a range-checking implementation is not standard
conforming, please provide some language from the standard supporting your
point of view. Note that the guarantee of arrays being contiguous is met by
such an implementation. What such an implementation prevents is just the
reinterpretation of array sizes through pointer casting and pointer
arithmetic.
OK, I agree that it is not explicitly guaranteed by the standard, but I
think in reality it always works. But as it was said, it is not
guaranteed by the standard.
Case closed.
I asked a similar question regarding C90 code in clc, since with very
few exceptions, C90 is a subset of C++03 and here are the question and
the answers I got:
Question:
Are the following guaranteed to work always as *C90* code?
1.
#include <stdio.h>
void some_func(int *p, const size_t SIZE)
{
size_t i;
for(i=0; i<SIZE; ++i)
printf("%d ", p[i]);
}
int main(void)
{
int array[10][5]= {0};
some_func(array[0], sizeof(array)/sizeof(**array));
puts("");
return 0;
}
The above prints 50 zeros. I think it is guaranteed to work, since all
arrays are sequences of their elements.
2.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t i, j;
int array[50]= {0};
int (*p)[5]= (int (*)[5])(&array[0]);
for (i= 0; i< 10; ++i)
for(j=0; j<5; ++j)
printf("%d ", p[i][j]);
puts("");
return 0;
}
Here p behaves as a 2-dimensional matrix, that is a 10x5 matrix. I think
it is guaranteed to work for the same reason as the first one, that is
we can treat an array (sequence) of integers as various types of integer
arrays.
=========================================================================
Answer 1:
It is not guaranteed to work.
The problem is that there is no array of int
with more than 5 members declared anywhere.
Pointers to char are allowed to step through the bytes
of any object, but that's by a special rule.
I can't conceive of any mechanism by which your code could fail,
but the guarantee of which you speak, is not there.
Your pointer to int,
is over running the boundaries of an array of 5 int.
-- pete
=========================================================================
Answer 2:
It's not necessarily legal, but it ought to be. There is a related
example at the end of 6.7.5.3 in the C99 Rationale (non-normative of
course),
----------------------------------------------------------------------
The following example demonstrates how to declare parameters
in any order and avoid lexical ordering issues
void g(double *ap, int n)
{
double (*a)[n]= (double (*)[n]) ap;
/* ... */ a[1][2] /* ... */
}
in this case, the parameter ap is assigned to a local
pointer that is declared to be a pointer to a variable
length array. The function g can be called as in
{
double x[10][10];
g(&x[0][0], 10);
}
----------------------------------------------------------------------
This sort of thing is common in numerical software and (some) authors
of the standard intended it to work, but the normative standard doesn't
necessarily guarantee it. Personally I would consider any failing
implementation as broken.
-- pa at panix dot com
.
|
|
|
|
|
|
|
|
|

|
Related Articles |
|