C++ sizeof

J

James Kanze

No, 'a' has the type 'int*[3]', and 'c' has the type 'int*[2]'. They
are _arrays_, not pointers. If your compiler supports decent typeid
information, you could output the type yourself
cout << typeid(a).name() << endl;
Although "a" and "c" are arrays, when we use their names as a values,
they are effectively pointers, aren't they?

No. They convert implicitly to pointers in contexts where an
array would be illegal. (Double's will implicitly convert to
char, as well. You wouldn't say that they are effectively
char's, I hope.)
 
J

Jess

Note that this is very error prone, as it will silently give
wrong results when applied to a pointer.

When we give an array name "a" to sizeof, "a" won't be treated as a
pointer. so when will 'sizeof' be wrongly applied to a pointer? can
you please give an example? thanks
 
J

Jess

No. They convert implicitly to pointers in contexts where an
array would be illegal.

But how do we know when an array is legal? since an array is treatd
as an array by "sizeof", then I guess it is legal to have an array for
"sizeof". As Victor has pointed out, an array's name is always
regarded as a pointer except when it is used in 'sizeof' or 'typeid',
or as a template argument, or as an initialiser of a reference of the
array type. Therefore, it seems arrays are illegal for most of the
situations. Under what circumstances are arrays illegal? this still
confuses me.
(Double's will implicitly convert to
char, as well. You wouldn't say that they are effectively
char's, I hope.)

Do you mean an array of doubles is converted to a pointer pointing to
chars?
Thanks
 
J

James Kanze

No. In this case, if 'foo' is called like 'foo(a)' and 'a' is, in
fact an array, the X will be deduced as the element type because 'a'
will decay. However, if you drop the asterisk, and then call 'foo'
with a true array, 'X' is likely to be deduced as "an array of N of
blah".

No.

First, there are only two contexts where using the name of an
array is relevant to template instantiation. The first is as a
template argument. In that context, it cannot match a type
argument (since the name of an array is not a type), and
templates cannot have non-type array arguments. In such cases,
it is matched more or less as it would be if it were a function
argument. The second is when it is an argument to a function,
one (or more) of whose overloads is a template, and type
deduction is applied. In such cases, the rules are such that if
the template function paramter is not a reference, an array type
will be converted to a pointer. The final result is that
template functions work pretty much like non template functions,
e.g.:

template< typename T >
void f( T x ) ;

int array[ 20 ] ;
f( array ) ;
instantiates:
void f( int* x ) ;

but

template< typename T >
void f( T& x ) ;

int array[ 20 ] ;
f( array ) ;
instantiates:
void f( int (&x)[20] ) ;

Note that template argument deduction can handle even more
complex cases. I can't imagine a professional programmer in C++
today, for example, who doesn't have something like the
following in his toolkit:

template< typename T >
T*
begin( T (&array)[ N ] )
{
return array ;
}

template< typename T >
T*
end( T (&array)[ N ] )
{
return array + N ;
}

template< typename T >
size_t
size( T (&array)[ N ] )
{
return N ;
}

They fournish C style arrays with some of the main functionality
of standard containers.
 
G

Gennaro Prota

If one needs to know the size of the array, one can always get it like
this:
sizeof(somearray) / sizeof(somearray[0])

Note that this is very error prone, as it will silently give
wrong results when applied to a pointer. In C++, a much better
solution is:

template< typename T, size_t N >
inline size_t
size( T (&array)[ N ] )
{
return N ;
}

Out of curiosity, is the lack of const-qualification on the parameter
intentional?

In my code, I'm using the following for quite some time now:

#include <cstddef>

namespace breeze {

template< typename T, std::size_t n >
std::size_t
count( const T ( & )[ n ] )
{
return n;
}

template< typename T >
typename T::size_type
count( const T & t )
{
return t.size();
}

}

And I've similar mini-templates for begin and end. Together, they
allow you to write things such as begin( a ), end( a ), count( a ),
regardless of whether a is a built-in array or a standard container:
that's one less thing to change if, for instance, you replace a
built-in array with tr1::array<>.
 
R

Ron Natalie

Jess said:
No, 'a' has the type 'int*[3]', and 'c' has the type 'int*[2]'. They
are _arrays_, not pointers. If your compiler supports decent typeid
information, you could output the type yourself

cout << typeid(a).name() << endl;

Although "a" and "c" are arrays, when we use their names as a values,
they are effectively pointers, aren't they?

No when you use their names, they are effectively arrays. This is a
grand misconception. What they do have is an implicit conversion to
pointer type. This is coupled with some rather long term C stupidities
that never got corrected in that arrays can not be assigned, passed by
value, or returned by value. The language provides a ghastly crutch
where an array type in a parameter of a function is silently changed
to pointer type.

? so "a" is the pointer
pointing to the first element of the array.

Untrue.
 
J

James Kanze

When we give an array name "a" to sizeof, "a" won't be treated as a
pointer. so when will 'sizeof' be wrongly applied to a pointer?

Why on earth should it? An array name is the name of an array.
In an expression, it has the type array[] of whatever; its value
is the contents of the array.

There aren't many things you can do with an array in C++, but
taking their size (or typeid) is one.
can you please give an example?

The classical example would be:

void
func( char array[10] )
{
std::cout << sizeof( array ) / sizeof( array[ 0 ] ) <<
std::endl ;
}

On my system, this outputs 0. It's an easy trap to fall into,
because it *looks* like the argument of the function is an
array. In fact, of course, C++ doesn't allow arrays to be
parameters of functions, and this is exactly equivalent to:

void
func( char* array )
{
std::cout << sizeof( array ) / sizeof( array[ 0 ] ) <<
std::endl ;
}

There is simply no array anywhere around.

My alternative version takes advantage of template argument
deduction. Since it requires an array, any attempt to use it on
a pointer will result in a compile time error.
 
J

James Kanze

On 27 Apr 2007 02:52:19 -0700, James Kanze <[email protected]>
wrote:
If one needs to know the size of the array, one can always get it like
this:
sizeof(somearray) / sizeof(somearray[0])
Note that this is very error prone, as it will silently give
wrong results when applied to a pointer. In C++, a much better
solution is:
template< typename T, size_t N >
inline size_t
size( T (&array)[ N ] )
{
return N ;
}
Out of curiosity, is the lack of const-qualification on the parameter
intentional?

More or less:). In fact, I rarely use this one, and only added
it for completeness' sake. In the ones I do use, begin() and
end(), the lack of const is very intentional; if the array is
const, the compiler will add it automatically, and if it's not,
I want non const pointers to be returned, so that I can use them
as arguments to e.g. std::transform. The reason why this one
isn't const is simply parallelism with the other two.
In my code, I'm using the following for quite some time now:
#include <cstddef>
namespace breeze {
template< typename T, std::size_t n >
std::size_t
count( const T ( & )[ n ] )
{
return n;
}
template< typename T >
typename T::size_type
count( const T & t )
{
return t.size();
}
}
And I've similar mini-templates for begin and end.

But without the const, I imagine:).

Note that about all the const buys you is that if you
instantiate once on int[10], and a second type on int const[10],
you only get one function, not two. As my functions are inline,
and I can't think of a case where the address (identity) would
be important, that doesn't bother me.
Together, they allow you to write things such as begin( a ),
end( a ), count( a ), regardless of whether a is a built-in
array or a standard container: that's one less thing to change
if, for instance, you replace a built-in array with
tr1::array<>.

Good point. I don't have the second versions, but then, I don't
write much generic code.
 
G

Gennaro Prota

On 27 Apr 2007 02:52:19 -0700, James Kanze <[email protected]>
wrote:
If one needs to know the size of the array, one can always get it like
this:
sizeof(somearray) / sizeof(somearray[0])
Note that this is very error prone, as it will silently give
wrong results when applied to a pointer. In C++, a much better
solution is:
template< typename T, size_t N >
inline size_t
size( T (&array)[ N ] )
{
return N ;
}
Out of curiosity, is the lack of const-qualification on the parameter
intentional?

More or less:). In fact, I rarely use this one, and only added
it for completeness' sake. In the ones I do use, begin() and
end(), the lack of const is very intentional; if the array is
const, the compiler will add it automatically, and if it's not,
I want non const pointers to be returned, so that I can use them
as arguments to e.g. std::transform.

Yes. I had const and non-const overloads for begin() and end(), until
I posted my previous reply and realized that was pretty useless :) So
I immediately updated the code:


<http://breeze.svn.sourceforge.net/viewvc/breeze/trunk/breeze/iteration/>

As to size() (or count(), as it's called in my library) the const
doesn't buy anything either. Stylistically, I like to have it, so I
asked whether your omission was unintentional or due to a different
view on style issues.
The reason why this one
isn't const is simply parallelism with the other two.
In my code, I'm using the following for quite some time now:
#include <cstddef>
namespace breeze {
template< typename T, std::size_t n >
std::size_t
count( const T ( & )[ n ] )
{
return n;
}
template< typename T >
typename T::size_type
count( const T & t )
{
return t.size();
}
}
And I've similar mini-templates for begin and end.

But without the const, I imagine:).

Er... see above :)
 
J

James Kanze

But how do we know when an array is legal?

The same way you know when anything is legal: the language
definition says what can and cannot be done with each type.
since an array is treatd as an array by "sizeof", then I guess
it is legal to have an array for "sizeof".
Right.

As Victor has pointed out, an array's name is always
regarded as a pointer except when it is used in 'sizeof' or 'typeid',
or as a template argument, or as an initialiser of a reference of the
array type.

I'm not sure Victor really knows the language. Some of his
posts seem to show great knowledge, and others contain blatent
misconceptions. In this case: an array's name is *never*
regardes as a pointer. *Never*, *ever*. An array's name is the
name of an array. When used in an expression, it has type
array[] of X. Always. Without exception.

Of course, C++ is noted for its lack of type safety, and it's
lossy conversions. Try something like:

double d = 3.14159 ;
std::string s ;
s = d ;

for example. Or for more fun:

int i = 0 ;
int const c = 0 ;
std::string s ;
s += i ;
s += c ;

The first is---surprisingly---fully defined and legal, although
it probably doesn't do what one might expect. (Actually, any
reasonable person would expect it to be illegal.) The second
has undefined behavior, and will generally result in a core
dump.

The problem isn't that d doesn't have type double, or that i or
c don't have type int. The problem is that C++ is just full of
unexpected and unintuitive implicit conversions. One of those
unexpected, unintuitive conversions is that an array implicitly
converts to a pointer. And in this case, two additional
"defects" in the language add to the conversion. The first is
that there are really very few things you can legally do with an
array---even indexation isn't legal on an array (but it is on a
pointer!). Which means that the implicit conversion occurs a
lot more often than one might like. The second is that
functions cannot have array types as parameters. If the
declaration of the parameter looks like an array, the compiler
automatically converts it into a pointer. Thus:

void f( int array[10] ) ;

declares a function which takes a pointer to an int as argument
(and not an array!), and the name array, within the function, is
not the name of an array, but of a pointer.
Therefore, it seems arrays are illegal for most of the
situations. Under what circumstances are arrays illegal?

I think you're approaching the problem backwards. C++ defines a
set of operators. For each operator, it defines what types are
legal as operands. Unless a type is defined as legal for a
specific operand, it is illegal.
this still confuses me.

It's one of the first things you have to learn, however. In any
statically typed language.
Do you mean an array of doubles is converted to a pointer pointing to
chars?

No. Doubles are converted to chars. Arrays of doubles are
arrays of doubles.
 
V

Victor Bazarov

Jess said:
If foo's signature is

void foo(X* a);

Then when I call foo(a), then the compiler knows foo is expecting a
pointer-type argument and hence array "a" decays to a pointer. Is this
what happens? If the signature of foo becomes

void foo(X a);

Then when I pass an array "a" to foo like "foo(a)", then does the
compiler always forbid "a" to decay? Why does a compiler do that? Is
array the only data structure with this behaviour?

This reminds me of another question. What I've learned before is that
if we have a function whose argument is not a pointer type or
reference type, then it's always pass-by-value. Following this
reasoning, it seems if we do "foo(a)", then the compiler will copy "a"
to "foo". Now the problem arises. What does it mean to copy an
array? Do we copy its contents or the address of its first element?

Yes, you're right. Arrays have no pass-by-value semantics, so I was
incorrect assuming that (X a) would cause 'X' to be deduces as 'array of'.
See James' reply as well. He elaborated on template argument deduction
well enough even for me to understand.
Can you please elaborate a bit on "as an initialiser of a reference
of the array type"? :)

int (&ra)[10] = a;

here 'a' does not decay.

Why does compiler do that?

Because the type of 'ra' is "a reference to an array". If 'a' were to
decay, there would be no way to verify the size. And I assume 'a' has
the size 10, otherwise it won't compile. IOW, for your original example
this would have to change to

int (&ra)[3] = a;

(since 'a' has size 3).
The different treatments to arrays are
very complicated, why would C++ compilers make things so hard?

I am guessing it's in attempts to make arrays (which are very, very
delapidated entities in C++ language) conform a bit more to the rest
of the language.
I
imagine there must be some reason behind it. more likely to do with
the way compilers work. Can you please tell me the reason behind all
of these complications, as it helps me to understand the strange
behaviour?

Again, I am not sure. I am guessing that there was the need to make
it work one way or another, and the designers just picked the way that
would work in the majority of cases. Simplicity is not always the
primary concern. In most problems you and I solve we just need to
remember that arrays decay into pointers, and if we need their size,
we just pass it along. Or better yet, don't use arrays, use standard
containers.

V
 
D

Default User

Victor said:
Jess said:
No, 'a' has the type 'int*[3]', and 'c' has the type 'int*[2]'.
They are arrays, not pointers. If your compiler supports decent
typeid information, you could output the type yourself

cout << typeid(a).name() << endl;

Although "a" and "c" are arrays, when we use their names as a
values, they are effectively pointers, aren't they?

If they are used in an expression other than in 'sizeof' or 'typeid',
or as a template argument, or as an initialiser of a reference of the
array type.

Also, rather importantly, when used with the address-of (&) operator.
So if you have:

int a[2];


Then &a, is NOT a pointer to pointer to int, but a pointer to array 2
of int.


I know you know that, of course.




Brian
 
V

Victor Bazarov

Default said:
Victor said:
Jess said:
No, 'a' has the type 'int*[3]', and 'c' has the type 'int*[2]'.
They are arrays, not pointers. If your compiler supports decent
typeid information, you could output the type yourself

cout << typeid(a).name() << endl;

Although "a" and "c" are arrays, when we use their names as a
values, they are effectively pointers, aren't they?

If they are used in an expression other than in 'sizeof' or 'typeid',
or as a template argument, or as an initialiser of a reference of the
array type.

Also, rather importantly, when used with the address-of (&) operator.
So if you have:

int a[2];


Then &a, is NOT a pointer to pointer to int, but a pointer to array 2
of int.


I know you know that, of course.

No, you're right, I had forgotten about it. Thanks for reminding.

V
 
D

Default User

Victor said:
Default said:
Victor said:
Jess wrote:
No, 'a' has the type 'int*[3]', and 'c' has the type
'int*[2]'. They are arrays, not pointers. If your compiler
supports decent typeid information, you could output the type
yourself

cout << typeid(a).name() << endl;

Although "a" and "c" are arrays, when we use their names as a
values, they are effectively pointers, aren't they?

If they are used in an expression other than in 'sizeof' or
'typeid', or as a template argument, or as an initialiser of a
reference of the array type.

Also, rather importantly, when used with the address-of (&)
operator. So if you have:

int a[2];


Then &a, is NOT a pointer to pointer to int, but a pointer to array
2 of int.


I know you know that, of course.

No, you're right, I had forgotten about it. Thanks for reminding.


Sure. You don't see the explicit use of addressing with arrays all that
often, but the implicit use comes up and causes significant confusion
(I think elsewhere here, in fact).

People often wonder about this:


void f(int **p)
{
}

int a[2][2];

f(a); // bzzzzzzt, not allowed.


That's because, of course, the conversion of array to pointer gives a
pointer to the first element, which in the above case is the same as
what I mentioned above.





Brian
 
K

kostas

Hello, if I have the following code that has an array of int*:

#include<iostream>
#include<string>
#include<cstring>
#include<cstddef>

using namespace std;

int main(){
int x = 1;
int y = 2;
int z = 3;

int* a[] = {&x,&y,&z};
int* c[] = {&x,&y};

cout << sizeof(a) << endl;
cout << sizeof(c) << endl;

return 0;

}

The output is 12 and 8. "sizeof" is supposed to return the number of
bytes its argument occupies according to its argument type, i.e.
without actually evaluating its argument. Since both "a" and "c" have
type "int**", why does "sizeof" return different values?

Hi all
I will contribute with an exercise. I hope you will find it pleasant.

Fill the following table:
sizeof("Jess") = ?
sizeof(*"Jess") = ?
sizeof(&*"Jess") = ?

How many hits did you have?
Regards
Kostas
 
O

Old Wolf

The classical example would be:

void
func( char array[10] )
{
std::cout << sizeof( array ) / sizeof( array[ 0 ] ) <<
std::endl ;
}

On my system, this outputs 0.

Is your system a Pentium 66? ;)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,197
Latest member
Sean29G025

Latest Threads

Top