14
#include<iostream>

int num[3]={66,77,88};

int main()
{
    int(*pi)[3]=&num;
    std::cout<<*pi;
} 

The result is an address instead of an array. What is the explanation behind this?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
air
  • 199
  • 1
  • 7
  • 4
    It is the entire array object. Since `cout` doesn't know how to print arrays, it gets converted to a pointer to the first element and printed as pointer. – HolyBlackCat Mar 30 '21 at 06:05
  • tl;dr: that's the same thing. The pointer to the first element of an array and the pointer to the array itself are usually the same (just with different types) – lucidbrot Mar 30 '21 at 06:05
  • `&num` is a pointer to the array `num`. And you define `pi` as a pointer to an array of three `int` elements. What else did you expect? Did you want to make a *copy* of the `num` array? – Some programmer dude Mar 30 '21 at 06:06
  • Also note that for any array or pointer `pi` and index `i` the expression `pi[i]` is exactly equal to `*(pi + i)`. Now if we use index `0` then that's `pi[0]` which is equal to `*(pi + 0)` which is equal to `*(pi)` which is equal to `*pi`. And since `pi` is a pointer to an array `pi[0]` is the array itself, and as any array it can decay to a pointer to its first element which is what happens here: `*pi` will decay to `&num[0]`. And it's that pointer that's being printed. – Some programmer dude Mar 30 '21 at 06:07

4 Answers4

18

not the entire array object?

*pi gives the array, i.e. the int[3]. But operator<< for std::basic_ostream doesn't have overloads taking array but has an overload taking pointer (const void*), then array-to-pointer decay happens, the converted pointer (int*) is pointing to the 1st element of the array and then is converted to const void*, then is passed to std::cout and gets printed out.

There is an implicit conversion from lvalues and rvalues of array type to rvalues of pointer type: it constructs a pointer to the first element of an array. This conversion is used whenever arrays appear in context where arrays are not expected, but pointers are:

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
4

Just like cout cannot print a vector, it cannot print an array. However cout will print the address of the first element.

If you take a look at the source code of iostream they mention cout as a pointer https://code.woboq.org/llvm/libcxx/src/iostream.cpp.html, and that is also why you always need to iterate over each item of your array as oppose to other languages (like Rust) where you can print a vector or an array.

For string there is an exception since in C and C++ they are (or should be!) NULL terminated, and that is why the whole char array is printed. But 0 is a valid number, hence there is no way to know the bounds of an int array unless you can detect it at compile time (which Rust does).

Antonin GAVREL
  • 9,682
  • 8
  • 54
  • 81
4

When an array such as int[3] is printed, it is passed to a function whose signature in principle takes the array as an argument. It could be any of

   ostream & operator <<(ostream &s, int *value);
   ostream & operator <<(ostream &s, int value[]);
   ostream & operator <<(ostream &s, int value[3]);

However, in C/C++ these are all equivalent to the first one. Passing an array as argument to a function just passes a pointer to the beginning of the array. Therefore, the size of the array is not (directly) known to the function.

If we consider a vector instead of an array, then the vector object itself knows its size and we can print the vector. There is no built-in method for this, so we would have to make one ourselves (in this case just for a vector of int to illustrate the idea):

#include <iostream>
#include <vector>

std::ostream &operator << (std::ostream &s, const std::vector<int> &v)
{
    s << "(";
    for(auto &e: v)
    {
        if(&e != &v[0])
        {
            s << ", ";
        }
        s << e;
    }
    s << ")";
    return s;
}

int main()
{
    std::vector<int> num = {66, 77, 88};
    std::cout << num;
}

The result is:

    (66, 77, 88)
nielsen
  • 5,641
  • 10
  • 27
2

It’s not something I would normally recommend, but, if you define an override of operator<<( ostream&, const int[3] ), the compiler will run that, instead of decaying the array pointer to void* or something else.

Here’s some sample code that demonstrates how C++ implicitly converts the right-hand side of operator<<.

#include <cstddef>
#include <cstdlib>
#include <iostream>

using std::ostream;
using std::ptrdiff_t;

template <ptrdiff_t N>
  ostream& operator<< ( ostream& out, const int (&a)[N] )
  {
    if constexpr (N > 1) {
      out << a[0];
      out << ", ";
      return ( out << *(const int(*)[N-1])(&(a[1])) );
    } else if constexpr (N == 1) {
      out << a[0];
    }

    return out;
  }

constexpr int nums[] = { 66, 77, 88 };
constexpr char letters[] = { 'a', 'b', 'c', 0 };
constexpr float reals[] = { 1.23f, 4.56f, 7.89f };

int main()
{
    std::cout << nums << '\n';
    static const int (*const pi)[3] = &nums;
    std::cout << *pi << std::endl;

    std::cout << (const char*)letters << '\n';
    std::cout << letters << std::endl;

    std::cout << (const void*)reals << '\n';
    std::cout << reals << std::endl;

    return EXIT_SUCCESS;
}

(You could certainly quibble with the implementation.)

If you provide an overload that exactly matches const int[3], it calls that. With a const char[4], the compiler tries decaying to a pointer of the element type, so the best match the compiler finds is const char*. When there is no match for const float[3] or const float*, the array decays to a const float* and implicitly converts to a void*.

Davislor
  • 14,674
  • 2
  • 34
  • 49