Как определить наибольший размер указателя на моей платформе? - PullRequest
5 голосов
/ 18 апреля 2019

В (C и) C ++ указатели на разные типы не обязательно имеют одинаковый размер . Я бы надеялся, что void * обязательно будет самым большим, но, похоже, даже это на самом деле не гарантировано.

Мой вопрос: Как я могу определить, какой максимальный размер указателя на моей платформе (цели компиляции)?

Примечание: я имею в виду любой указатель, включая указатели на функции-члены класса; вещи, которые вы можете получить с помощью оператора &. Я не имею в виду сущности, которые «в разговорной речи» известны как указатели, то есть не unique_ptr или shared_ptr и т. Д.

Ответы [ 2 ]

4 голосов
/ 18 апреля 2019

Существует 3 различных типа указателей, которые могут иметь разный размер:

  • указатель на объект
  • указатель на функцию
  • указатель на функцию-член

A void * гарантированно будет достаточно большим, чтобы вместить каждый указатель на объект в соответствии со стандартом C ++ 17 6.9.2.5:

Указатель наcv-qualified ([basic.type.qualifier]) или cv-unqualified void могут использоваться для указания на объекты неизвестного типа.Такой указатель должен содержать любой указатель объекта.Объект типа cv void * должен иметь те же требования к представлению и выравниванию, что и cv char *.

class A;

typedef void (A::*a_func_ptr)(void);
typedef void (*func_ptr)(void);

size_t a = sizeof(a_func_ptr), b = sizeof(func_ptr), c = sizeof(void*);

std::cout << std::max(a, std::max(b, c)) << std::endl;

должен выполнять эту работу.

edit: C ++ 17Стандарт 6.9.2.3 гласит:

За исключением указателей на статические элементы, текст, ссылающийся на «указатели», не применяется к указателям на элементы.

Итак, самый большой возможный указательэто либо void *, либо указатель на функцию:

std::cout << std::max(sizeof(void*), sizeof(void(*)(void))) << std::endl;
3 голосов
/ 18 апреля 2019

Существует четыре совершенно не связанных класса типов указателей в языке C ++: указатели объектов, указатели на функции, указатели на нестатические элементы данных и указатели на нестатические функции-члены.Термин «указатель» обычно применяется только к объектам и типам указателей на функции [basic.compound] / 3 :

[…] За исключением указателей на статические элементы, текст относится к«Указатели» не относятся к указателям на участников.[…]

Указатели и указатели на нестатические элементы фактически рассматриваются как два совершенно разных вида составных типов [basic.compound] / 1 (что имеет смысл, посколькунестатические указатели на элементы больше похожи на относительные смещения и меньше похожи на фактические адреса).

За исключением условно поддерживаемого преобразования между указателями на объекты и функции, семантика которого (если поддерживается вообще) будет реализациейопределенный [expr.reinterpret.cast] / 8 , нет никакого способа преобразовать между этими четырьмя классами типов указателей.

Однако стандарт действительно определяет взаимозаменяемость среди указателей объекта [expr.reinterpret.cast] / 7 , взаимопревращаемость среди указателей функций [expr.reinterpret.cast] / 6 , взаимопревращаемость среди указателей элементов данных [expr.reinterpret.cast] /10.2 и взаимопревращаемость среди указателей на функции-члены [expr.reinterpret.cast] /10.1.

В результате, хотя нет общего типа указателя, с которым в общем случае связаны все другие типы указателей, это вполне определенное поведение - приводить любой указатель объекта к какому-либо произвольному типу указателя объекта и обратно.Это хорошо определенное поведение - приводить любой указатель функции к какому-либо произвольному типу указателя функции и обратно.Это хорошо определенное поведение - приводить любой указатель на элемент данных к некоторому произвольному типу указателя на элемент данных и обратно.И это хорошо определенное поведение - приводить любой указатель на функцию-член к какому-либо произвольному типу указателя на функцию-член и обратно.И единственное, что объединяет все эти разные классы типов указателей, это то, что все они являются типами объектов [basic.types] / 8 .

Хотя это не гарантирует, что, например,все типы указателей на функции-члены имеют одинаковый размер, это неявно устанавливает, что любой объект некоторого типа указателя на функцию-член может эффективно использоваться для хранения любого значения указателя на функцию-член.Все еще могут быть типы указателей на функции-члены, более крупные, чем другие, но они не могут содержать больше информации, чем другие, поскольку стандарт требует, чтобы преобразование в и из любого другого типа указателя на функцию-член не потеряло информацию (исходное значение всегда можно восстановить).Тот же аргумент работает аналогично для всех других классов типов указателей.

Исходя из всего этого, я бы сказал, что технически невозможно найти «самый большой тип указателя» в стандарте C ++.Тем не менее, хотя технически невозможно найти самый большой тип указателя сам, основываясь на приведенном выше аргументе, определенно возможно найти верхнюю границу для объема памяти, необходимого для надежного хранения любого значения типа указателя.Хотя эти два понятия технически разные, на практике второй, скорее всего, почти так же хорош, как и первый (никакой разумный компилятор просто не будет случайным образом добавлять множество битов заполнения к представлению значения некоторого типа указателя только потому, что это технически допустимо.).По крайней мере, я с трудом представляю себе, что еще, кроме хранения значений указателей, вы, возможно, захотите делать с запрашиваемой информацией.

Используя, например,

using generic_obj_ptr = void*;
using generic_fun_ptr = void (*)();

class dummy_t;
using generic_dat_mem_ptr = dummy_t dummy_t::*;
using generic_mem_fun_ptr = void (dummy_t::*)();

Вы можете вычислить

auto obj_ptr_size = sizeof(generic_obj_ptr_t);
auto fun_ptr_size = sizeof(generic_fun_ptr_t);
auto dat_mem_ptr_size = sizeof(generic_dat_mem_ptr_t);
auto mem_fun_size = sizeof(generic_mem_fun_ptr_t);

auto max_ptr_size = std::max({ sizeof(generic_obj_ptr_t), sizeof(generic_fun_ptr_t), sizeof(generic_dat_mem_ptr_t), sizeof(generic_mem_fun_ptr_t) });
auto max_ptr_align = std::max({ alignof(generic_obj_ptr_t), alignof(generic_fun_ptr_t), alignof(generic_dat_mem_ptr_t), alignof(generic_mem_fun_ptr_t) });

или просто использовать

using ptr_storage_t = std::aligned_union<0U, generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;

или даже

using any_ptr_t = std::variant<generic_obj_ptr_t, generic_fun_ptr_t, generic_dat_mem_ptr_t, generic_mem_fun_ptr_t>;

или в чистом виде:

using any_ptr_t = std::variant<void*, void (*)(), dummy_t dummy_t::*, void (dummy_t::*)()>;

в качестве хранилища, в котором любое значение указателя объекта может быть сохранено при приведении к void* и выше, любое значение указателя функции может быть сохранено при приведении к void (*)() и от него, любой указатель члена данных может быть сохранен при приведении к и отdummy_t dummy_t::*, и любой указатель на функцию-член может быть сохранен при приведении к void (dummy_t::*)().

играть с ним здесь

Задача обернуть это вКласс, который берет на себя все функции для хранения произвольных значений любого типа указателя (не забудьте разобраться с возможной квалификацией cv), должен быть оставлен в качестве упражнения для читателя, в основном потому, что я действительно хотел бы хорошо выспаться сегодня вечером…

...