Это просто безумие!
Чтобы понять, как объекты хранятся в памяти на C++, я пишу следующий код, чтобы увидеть адреса переменных.
Но все становится еще более запутанным.
Так что в дальнейшем мне интересно
почему 1 отличается от 2 ------ значит ли это, что это указатель? но почему?
почему 1 отличается от 3 ------ это передается по ссылке, они должны быть одинаковыми?!
#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
}
редактировать: мне очень жаль, разница между 5 и 6 не соответствует действительности, я случайно удалил push_back, мне было так жаль вас, надеюсь, это вас не сильно смутило.
добавьте в свой компилятор флаги -Wall
-Werror
. Обратите внимание, что printf
не обеспечивает проверку типов.
@MarekR, добавив эти флаги, не обнаружит все UB.
@JHBonarius обнаружит, что строка формата не соответствует аргументам: godbolt.org/z/osc8Eo
Для меня самый важный вопрос — почему &persons[0]
и &p[0]
не одно и то же. Пробовал код на колиру, но там они одинаковые. Тем не менее, используйте std::cout
, чтобы избежать проблем с varargs и форматированными типами.
@molbdnilo Я предполагаю, что передача чего-то, а не указателя на% p, является неопределенным поведением, но как объяснить, что 5 отличается от 6? Это просто обычный адрес объекта в куче, почему отличается?
Отвечает ли это на ваш вопрос? Когда printf — это адрес переменной, зачем использовать void*?
@GFL 5 и 6 выводят адрес первого элемента в базовом хранилище. Это будет меняться каждый раз, когда хранилище перераспределяется.
@stefaanv УБ. вот исправленный пример.
@molbdnilo Думаю, ты прав, спасибо за помощь!
Спецификатор формата %p
требует, чтобы аргумент имел тип void*
. Если вы передаете аргумент неправильного типа, то поведение программы не определено.
printf(" persons: %p\n", persons);
Здесь вы передаете аргумент типа vector<A>
, поэтому поведение программы не определено.
Поскольку аргумент не является даже указателем, нет никакой надежды на осмысленный вывод.
printf(" persons: %p\n", &persons);
Здесь вы передаете аргумент типа vector<A>*
, поэтому поведение программы не определено.
В этом случае аргумент является как минимум указателем. В системах, где все указатели имеют одинаковое представление, вывод может иметь смысл и представлять адрес, по которому хранится указанный объект. Однако это по-прежнему нарушает предварительное условие типа аргумента, и на поведение нельзя полагаться.
Выводы:
printf
, иначе поведение будет неопределенным. Внимательно изучите документацию.printf
. Его сложно использовать правильно, и есть более простые альтернативы.@JHBonarius Они могут быть. Но здесь они не конвертируются, так как параметр не типа void*
. printf
— это функция с переменным числом аргументов, и параметры не имеют типов как таковых.
Я удалил свой комментарий, так как вижу, что ошибаюсь. Может быть, это было интересно для обсуждения. Для меня интересно, что printf("persons: %p\n", std::addressof(persons));
тогда тоже будет UB... не ожидал такого...
@JHBonarius В системах со словесной адресацией (т. Е. Старых или экзотических) указатели разных типов могут иметь разные размеры, которые могут быть меньше, чем void*
, и если такой меньший тип передается в printf
, то аргументы не будут иметь макет, который printf
ожидает. В этом случае случится что-то плохое. Если представление такое же, то поведение по-прежнему технически неопределенно, хотя на практике я не ожидаю сюрпризов.
Вы написали «Потому что вы вставили элемент в вектор между ними», но между операторами 5 и 6 элемент не был вставлен. Оба оператора были выполнены после p.push_back(a1);
в функции insert()
.
@JHBonarius Ты прав. Я неправильно прочитал код. Убрал некорректный ответ.
@jjramsey то же самое ^^^
Передача чего-то, что не является указателем на
printf
, с помощью спецификатора%p
имеет неопределенное поведение. Приличный компилятор должен предупредить об этом. Невозможно напечататьstd::vector
сprintf
.