Почему выходные данные 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';
Что бы вы ни пытались сделать, если ваш компилятор не предупредил вас о несоответствии типов, вам следует включить больше предупреждений. Это особенно верно для тех, кто кодирует методом проб и ошибок, пока программа не скомпилируется.