Почему выходные данные printf и std :: cout отличаются?

Я попробовал следующий код C++. Однако выходные данные printf и std::cout отличаются. Зачем?

struct Foo
{
    int a;
    int b;
    int c;
};

int main()
{
    printf("%d\n", &Foo::c);  // The output is 8
    std::cout << &Foo::c << "\n"; // The output is 1
}

Ответ 1

printf("%d\n", &Foo::c): это неопределенное поведение, так как &Foo::c не целое число, а указатель на член (но на самом деле обычно компилятор хранит указатель на данные член в качестве смещения, а так как 8 является смещением Foo::c, 8 печатается).

std::cout << &Foo::c: это печатает значение &Foo::c. Поскольку у iostream нет указателя на принтер-член, он выбирает ближайший: он преобразует его в bool и печатает его как целое число. Поскольку &Foo::c преобразованный в bool имеет значение true, bool 1.

Ответ 2

Вывод отличается, потому что поведение вашего printf не определено.

Указатель на член (например, полученный из &Foo::c) не является целым числом. Функция printf ожидает целое число, так как вы сказали это тоже с помощью спецификатора %d.

Вы можете изменить его, добавив приведение к bool, например так:

printf("%d\n", (bool)&Foo::c)

Указатель на член может быть преобразован в bool (что вы делаете с приведением), и затем bool подвергается интегральному продвижению до int из-за того, что он является целочисленным переменным аргументом переменной variadic.

Говоря о преобразовании в bool, это именно то преобразование, которое применяется неявно при попытке вызвать std::ostream operator<<. Поскольку нет перегрузки оператора, который поддерживает указатели на элементы, разрешение перегрузки выбирает другое, которое можно вызвать после неявного преобразования &Foo::c в логическое значение.

Ответ 3

В дополнение к более буквальному ответу о том, почему компилятор интерпретировал ваш код так, как он это сделал: вы, похоже, столкнулись с проблемой XY. Вы пытаетесь отформатировать указатель на член как целое число, что настоятельно рекомендует вам сделать что-то другое.

Если то, что вы хотели, было значением int хранящимся в .c, то вам нужно либо создать экземпляр Foo some_foo; и возьмите some_foo.c, иначе вам нужно объявить Foo::c static членом, так что существует один однозначный Foo::c для всего класса. Не берите адрес в этом случае.

Если вы хотели получить адрес .c члена некоторого Foo, вы должны сделать то же, что и выше, чтобы Foo::c был static и ссылался на одну конкретную переменную, или же объявить экземпляр и взять его .c член, затем возьмите адрес. Правильный printf() для указателя объекта - это %p, и для вывода представления указателя объекта с помощью <iostream> преобразуйте его в void*:

printf( "%p\n", &some_foo.c );
std::cout << static_cast<void*>{&some_foo.c} << '\n';

Если вам нужно смещение Foo::c в классе Foo, вам нужен макрос offsetof() в <stddef.h>. Так как его возвращаемое значение равно size_t, которое не совпадает с размером int на 64-битных платформах, вы можете либо явно привести результат, либо передать printf() спецификатор типа z:

#include <stddef.h>

/* ... */

  constexpr size_t offset_c = offsetof( Foo, c );
  printf( "%zu\n", offset_c );
  cout << offset_c << '\n';

Что бы вы ни пытались сделать, если ваш компилятор не предупредил вас о несоответствии типов, вам следует включить больше предупреждений. Это особенно верно для тех, кто кодирует методом проб и ошибок, пока программа не скомпилируется.