4

That is just crazy!
In order to figure out how objects store in memory in C++,
I write the following code to see the address of variables.
But things get more confused.
So in the following I wonder

why 1 is different from 2 ------does this means that it's a pointer? but why?
why 1 is different from 3 ------it's passed by reference, they should be same?!

#include <iostream>
#include <vector>
using namespace std;

class A {
 public:
  A(int age, string name) : age_(age), name_(name){};
  void SetAge(int age) {
      age_ = age;
  }
  
  int age_;
  string name_;
};

void insert(vector<A>& p) {//passed by reference
    A a1{1, "tom"};
    printf(" p: %p\n", p); // p: 0x7ffc6cc98080 ------------3
    printf("&p: %p\n", &p);//&p: 0x7ffc6cc981a0 ------------4

    printf("&a1: %p\n", &a1); // &a1: 0x7ffc6cc980c0 /on stack, that's no problem

    p.push_back(a1);

    printf("&p[0]: %p\n", &p[0]); // &p[0]: 0x55b54cdd02c0 /on heap, that's no problem
}

int main()
{
    vector<A> persons;
    printf(" persons: %p\n", persons); // persons: 0x7ffc6cc981c0 ------------1
    printf("&persons: %p\n", &persons);//&persons: 0x7ffc6cc981a0 ------------2

    insert(persons);

    printf("&p[0]: %p\n", &persons[0]);// &p[0]:  0x55b54cdd0350
    printf("persons: %p\n", persons); // persons: 0x7ffc6cc981c0 /same as above
    cout << persons.size() << endl; //1
    
}

edit: I'm so sorry for that, the difference between 5 and 6 is not true, I delete a push_back accidentally, I felt so sorry for you, hope that didn't confused you much.

G FL
  • 43
  • 4
  • 9
    Passing something that's not a pointer to `printf` with the `%p` specifier has undefined behaviour. A decent compiler should warn about this. There is no way of printing a `std::vector` with `printf`. – molbdnilo Dec 09 '20 at 16:01
  • 1
    add to your compiler flags `-Wall` `-Werror`. Note that `printf` do not provide type checking. – Marek R Dec 09 '20 at 16:05
  • @MarekR adding those flags will not detect all UB. – JHBonarius Dec 09 '20 at 16:13
  • @JHBonarius it will detect that format string do not match to arguments: https://godbolt.org/z/osc8Eo – Marek R Dec 09 '20 at 16:14
  • To me, the most important question is why `&persons[0]` and `&p[0]` are not the same. I tried the code on coliru, but there they are the same. Still, use `std::cout` to avoid problems with the varargs and formatted types. – stefaanv Dec 09 '20 at 16:15
  • @molbdnilo I assume it's undefined behavior to pass something not a pointer to %p, but how to explain the 5 is different from 6?, they are just normal object's address on the heap, why different? – G FL Dec 09 '20 at 16:17
  • 1
    Does this answer your question? [When printf is an address of a variable, why use void\*?](https://stackoverflow.com/questions/7290923/when-printf-is-an-address-of-a-variable-why-use-void) – JHBonarius Dec 09 '20 at 16:18
  • @GFL 5 and 6 print the address of the first element in the underlying storage. This will change every time that storage gets reallocated. – molbdnilo Dec 09 '20 at 16:21
  • @stefaanv UB. [here's the fixed example](https://godbolt.org/z/8fo6do). – JHBonarius Dec 09 '20 at 16:22
  • @molbdnilo I think you are right, thanks for your help! – G FL Dec 09 '20 at 17:01

1 Answers1

9

The format specifier %p requires that the argument is of type void*. If you pass an argument of wrong type, then the behaviour of the program is undefined.

printf(" persons: %p\n", persons);

Here, you pass an argument of type vector<A>, therefore the behaviour of the program is undefined.

Since The argument isn't even a pointer, there is no hope of meaningful output.

printf(" persons: %p\n", &persons);

Here, you pass an argument of type vector<A>*, therefore the behaviour of the program is undefined.

In this case the argument is at least a pointer. On a systems where all pointers have the same representation, the output might be meaningful and represent the address where the pointed object is stored. However, this still violates the precondition of the argument type and the behaviour should not be relied upon.


Conclusions:

  • Don't use wrong types with printf or else the behaviour will be undefined. Study the documentation carefully.
  • There are no correct format specifiers for any class types.
  • Don't use printf. It is difficult to use correctly and there are easier alternatives.
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    @JHBonarius They *can* be. But they aren't converted here, since the parameter isn't of type `void*`. `printf` is a vararg function and the parameters don't have types as such. – eerorika Dec 09 '20 at 16:08
  • I deleted my comment, as I see I'm wrong. Maybe it was interesting for the discussion. For me it's interesting that `printf("persons: %p\n", std::addressof(persons));` would then also be UB... wouldn't expect that... – JHBonarius Dec 09 '20 at 16:11
  • @JHBonarius On word-addressed (i.e. old or exotic) systems pointers of different types can have different sizes which may be smaller than `void*` and if such smaller type is passed to `printf`, then the arguments wouldn't have the layout that `printf` expects. In this case bad stuff would happen. If the representation is same, then behaviour is still technically undefined although in practice I would not expect surprises. – eerorika Dec 09 '20 at 16:14
  • 1
    You wrote "Because you inserted an element into the vector in between," but between statements 5 & 6, no element was inserted. Both statements were executed after `p.push_back(a1);` in the `insert()` function. – jjramsey Dec 09 '20 at 16:32
  • @JHBonarius You're right. I misread the code. Removed the buggy answer. – eerorika Dec 09 '20 at 16:35
  • @jjramsey ditto ^^^ – eerorika Dec 09 '20 at 16:35