Предположим, что у нас есть следующие два неравенства внутри функции-члена
this <= (void *) &this->data_member
и
&this->data_member < (void *) (this+1)
Гарантированно ли они верны? (Кажется, они верны в нескольких случаях, которые я проверил.)
Обновлено: я пропустил амперсанды, теперь это правильная форма неравенства.
Каковы были бы ваши планы, если бы конкретный компилятор C++ использовал, например, один за концом фактического объекта в качестве фактического значения указателя памяти для объекта?
@ Öö Tiib, Никаких амперсандов не пропало, сейчас поправили.
Я полагаю, вы имеете в виду this <= (void *)(&this->data_member) и (void *)(&this->data_member) < (void *)(this + 1). В любом случае стандарт определяет только результаты операторов сравнения, применяемых к членам. this не является членом, и ничего не говорится о связи между this и адресом его нестатических элементов, т.е. поведение не указано. На практике для доступа к нестатической структуре или членам класса, вероятно, потребовалась бы довольно забавная бухгалтерия, если бы члены не были размещены в памяти, на которую указывает this.
Нетрудно увидеть контрпример в природе, если data_member на самом деле является членом данных виртуальной базы.
@ T.C. Я пытался создать контрпример с виртуальной базой, но пока у меня ничего не вышло. Не могли бы вы показать это?
@ user10636819 Ну вот
@Caleth Спасибо, это хороший пример. Это также немного странно, поскольку эти неравенства, кажется, снова удовлетворяются, если тестировать непосредственно внутри структуры D, а не путем вызова функций-членов базовых классов. Я все еще изучаю C++ (и еще не понимаю виртуального наследования), но я предполагаю, что это подразумевает неопределенное поведение.
@ user10636819 sizeof(D) не является sizeof(B) + sizeof(C), потому что у него только одна база A. Есть некоторая забавность, позволяющая преобразовать D* в B*, что, вероятно, реализовано представлением B, имеющим только указатель на его базу A.
Было бы полезно, если бы вы сказали, заботитесь ли вы только о полных объектах и членах массива или произвольных подобъектах.
@ user10636819 "и еще не разбираюсь в виртуальном наследовании" Регулярное наследование - это исключительное отношение, подобное членству. Виртуальное наследование - это отношение общего наследования, поэтому оно сильно отличается. Виртуальное наследование похоже на виртуальную функцию, его можно переопределить (в некотором смысле): это означает, что отношение наследования между B и A переопределяется в D. Таким образом, B в D не создается как полный объект B. Без виртуального наследования базовый класс строится как законченный объект.





Из проекта стандарта CPP 4713:
6.6.2 Object model [intro.object]/7
An object of trivially copyable or standard-layout type (6.7) shall occupy contiguous bytes of storage.12.2 Class members [class.mem]/18
Non-static data members of a (non-union) class with the same access control (Clause 14) are allocated so that later members have higher addresses within a class object.12.2 Class members [class.mem]/25
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any).
Взяв все вышеперечисленное вместе, мы можем сказать, что первое уравнение справедливо, по крайней мере, для тривиально копируемых объектов.
Также из онлайн ссылка cpp:
The result of comparing two pointers to objects (after conversions) is defined as follows:
1) If two pointers point to different elements of the same array, or to subobjects within different elements of the same array, the pointer to the element with the higher subscript compares greater. In other words, they results of comparing the pointers is the same as the result of comparing the indexes of the elements they point to.
2) If one pointer points to an element of an array, or to a subobject of the element of the array, and another pointer points one past the last element of the array, the latter pointer compares greater. Pointers to single objects are treated as pointers to arrays of one:&obj+1compares greater than&obj(since C++17)
Итак, если ваш data_member является указателем нет и ему не была выделена память отдельно, уравнения, которые вы опубликовали, остаются в силе.
по крайней мере для тривиально копируемых объектов.
Тонкость и проблема в том, что программа обычно не может протестировать ее сама, потому что тогда ее поведение будет неопределенным.
@StoryTeller Как ваше утверждение связано с вопросом или ответом?
@ PeterA.Schneider - Прямо из вопроса: «В некоторых случаях, которые я проверил, они кажутся правдивыми». Как вы себе представляете, что OP это проверяет?
Спасибо! Это гарантирует выполнение второго неравенства. А как насчет первого (он сравнивает элементы объекта с его собственным адресом)?
@ user10636819: «Если объект класса стандартной компоновки имеет какие-либо нестатические элементы данных, его адрес такой же, как адрес его первого нестатического члена данных». и «Нестатические элементы данных (не объединенного) класса с одинаковым контролем доступа выделяются таким образом, чтобы более поздние члены имели более высокие адреса в объекте класса».. Взятые вместе, это будет означать, что первое также верно.
@StoryTeller Логический результат сравнения - true, который легко проверить в программе. Это не имеет ничего общего с тем, правильна программа или нет, и тем более с самой программой. (Если OP преуспеет в тот, он немедленно получит предложение от прачечной, от которого он не сможет отказаться ;-).)
@ PeterA.Schneider - Логический результат этого выражения не определен стандартом, я не знаю, каково ваше определение «правильного», мое приближение к этому начинается с того, дает ли стандарт нам спецификацию, чтобы рассуждать об этом. . Если мы не можем рассуждать об этом действительно портативным способом, обсуждение «правильности» бессмысленно.
@StoryTeller Вы сказали, что «программа обычно не может протестировать ее сама», и хотя это правда, это не имеет никакого отношения к вопросу или ответу: в обоих случаях мы люди пытается установить, является ли программа действительной на C++, и если да, имеет ли он определенное поведение, и если да, то указано ли это поведение. Ни в коем случае программа не пытается себя протестировать. в этой программе все три.
@ PeterA.Schneider - Никто не встал на сторону против P.W. И я отказываюсь, чтобы меня затащили в эту кроличью нору. Вы все еще не понимаете, как связан мой комментарий? Что ж, я думаю, это очень плохо. Добрый день.
@StoryTeller Вы сказали: «Логический результат этого выражения не определен стандартом», что, по-видимому, противоречит выводу П.В. «Уравнения, которые вы опубликовали, остаются в силе», если я не неправильно читаю одного из вас.
Наличие одного и того же адреса не гарантирует, что он будет иметь эквивалентное частичное упорядочение при преобразовании в void*. this не указывает на нестатический член данных, и поэтому результат сравнения не указан. Для типов стандартного макета нет специального предложения.
Полный стандартный текст составляет:
[expr.rel] - 4: The result of comparing unequal pointers to objects82 is defined in terms of a partial order consistent with the following rules:
Здесь мы имеем дело с частичным порядком, а не с полным порядком. Это означает, что a < b и b < c подразумевают a < c, но не более того.
(В примечании 82 указано, что для этой цели объекты, не являющиеся массивом, считаются элементами одноэлементного массива, с интуитивным значением / поведением «указатель на элемент, следующий за концом»).
(4.1) If two pointers point to different elements of the same array, or to subobjects thereof, the pointer to the element with the higher subscript is required to compare greater.
Указатели на разные элементы не являются указателями на элементы (подобъекты) одного и того же массива. Это правило не действует.
(4.2) If two pointers point to different non-static data members of the same object, or to subobjects of such members, recursively, the pointer to the later declared member is required to compare greater provided the two members have the same access control ([class.access]), neither member is a subobject of zero size, and their class is not a union.
Это правило связывает указатели только на элементы данных одного и того же объекта, но не другого объекта.
(4.3) Otherwise, neither pointer is required to compare greater than the other.
Таким образом, вы не получаете никаких гарантий от стандарта. Другой вопрос, сможете ли вы найти реальную систему, в которой вы получите результат, отличный от ожидаемого.
Я бы просто добавил, что для стандартные классы верстки указатель на его экземпляр / объект может быть интерпретирован как указатель на его первую нестатическую переменную-член. И все другие переменные-члены должны храниться по большим адресам. Это означает, что оба условия OP гарантированно выполняются, но только в этом частном случае.
Кроме того, для тривиально копируемые классы все элементы данных должны храниться между указателем на объект и тем же, увеличенным на его размер в байтах (что подразумевает то же самое). В противном случае нам бы не разрешили memcpy такие объекты.
Это был бы мой неожиданный ответ («не связанные объекты, никаких гарантий»); но когда мы считаем, что «отдельные объекты эквивалентны массивам размером 1» плюс «1-последний определяется как большее», мы, как я полагаю, получаем ответ PW, потому что «& x + 1» не является «несвязанным» "но один-прошедший-массив, к которому применяется 4.1.
@ PeterA.Schneider Что, если бы для нетривиально копируемого класса нестандартной компоновки указатель на его экземпляр был бы внутренне определен как адрес его последнего байта в памяти. Есть ли что-нибудь в Стандарте, запрещающее такое применение?
@DanielLangr Указатель на объект (т.е. кусок типизированной памяти) всегда указывает на его первый байт. [Также: как бы вы «внутренне определили»?] Не уверен, что могу указать для этого стандартное место; но нарушение этой основной идеи, похоже, нанесет ущерб ряду инвариантов (::free(void *) !?). То, что мы находим там, не гарантируется, и предположения OP верны только для нестатического, ну, данные-члены, но я не вижу требования для «тривиально копируемого» или «стандартного макета». [ctd.]
[ctd .:] 12.2 / 17 в n4659 прямо говорит: «Нестатические элементы данных класса (не объединенного) с тем же контролем доступа (пункт 14) выделяются так, чтобы более поздние члены имели более высокие адреса внутри объекта класса (выделено мной) Здесь особо не о чем говорить.
@ PeterA.Schneider Это может быть чрезвычайно педантично, но где в стандарте указывается, что означает «первый» в "его адрес такой же, как и адрес его первого нестатического элемента данных"? Конечно, все мы знаем, что имеется в виду, но разве это где-нибудь уточняется?
@MaxLanghof Педантичный ответ, конечно, таков: это элемент, адрес которого совпадает с адресом объекта. ;-) Но если серьезно: есть гарантия, что элементы расположены в порядке их объявления (в отличие, например, от оценки параметров функции, которая не указана, IIRC).
@ PeterA.Schneider Эта гарантия существует только для членов данных с тем же уровнем доступа. В частности, не уточняется, приходят ли ваши участники private раньше членов public или наоборот. Таким образом, я мог довести педантизм до крайности и сказать, что «первый» всегда означает «частные члены», даже несмотря на то, что в моей реализации частные члены имеют более высокие значения указателей, чем публичные.
@DanielLangr "будет внутренне определен как адрес" Что значит "внутренне"? Представление указателя - это деталь реализации: вы не можете интерпретировать указатель как другой тип указателя и ожидать чего-либо разумного. У вас могут быть указатели на 8-байтовый выровненный тип, у которого для развлечения установлены 3 младших бита, но всегда маскируются при разыменовании. Вы увидите, что эти биты устанавливаются только с помощью каламбура или проверки представления (reinterpret_cast<void**>, union, memcpy) или, возможно, reinterpret_cast<int>.
IOW, можно ли измерить внутренне определенный адрес с помощью конструкции с четко определенным результатом? Что будет делать неявное преобразование этих указателей в void* или reinterpret_cast<char*>? Будет ли результат указывать на последний байт или это будет изменено, чтобы скрыть «внутреннее представление»?
"Что делать, если для нестандартного макета нетривиально копируемого класса" класс под названием T "указатель на его экземпляр будет внутренне определен как адрес его последнего байта в памяти" Что произойдет с таким кодом? void *raw = operator new(sizeof T); T *p = new(raw) T; p->~T(); operator delete(/*note: not raw*/ p);
@curiousguy Согласитесь, это, скорее всего, неверный пример. Просто примечание: не будет ли здесь delete p; неопределенным поведением? Аргументом operator delete может быть только указатель, полученный operator new, которым здесь является raw.
@DanielLangr Я никогда не рассматривал возможность того, что new(raw) T != raw.
Значение объекта находится в его представлении, и это представление представляет собой последовательность символов без знака: [basic.types] / 4
The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). The value representation of an object of type T is the set of bits that participate in representing a value of type T.[...]
Итак, для фундаменталистов формализма верно, что ценность не определено, но этот термин появляется в определении доступ: [defns.access]:
read or modify the value of an object
Так является ли значение подобъекта частью значения полного объекта? Полагаю, это то, что предусмотрено стандартом.
Сравнение должно быть верным, если вы приводите указатели объектов к unsigned char*. (Это обычная практика, которая не соответствует спецификации основной выпуск # 1701)
Да для любого объекта полный.
@curiousguy Ты про тип?
Вы даже не можете выполнять арифметические операции с указателем неполного типа, AFAIK, поэтому нет p+1. Я имею в виду законченный объект.
Незавершенного объекта нет! См. intro.object. Но действительно, если ссылка или указатель относятся к неполному типу, с ними мало что можно сделать.
Что вы имеете в виду под «законченным объектом»?
«Да для любого готового объекта» Я имел в виду, что «Объектное представление объекта типа T ...» должно читать «полный объект». [basic.types] / 4 здесь неверно (IMNSHO)
"Объект создается определением, новым выражением ([expr.new]), при неявном изменении активного члена объединения или при создании временного объекта ([conv.rval], [class.porary] )" Итак, вы согласны с тем, что каждый учебник, когда-либо написанный на C++, ошибочен, Stroustrup ошибается, std неверен WRT, конвертирующий программы C в C++ (смеется). Вы действительно хотите поддержать это утверждение? Юрист никогда не собирался относиться к тексту всерьез по отношению к любому другому источнику на C++.
Кроме того, «при неявном изменении активного члена союза» НЕ присутствовало более ДЕСЯТИЛЕТИЯ. Так кто-нибудь серьезно относится к этому предложению? Действительно? Это так неправильно! Кроме того, «объект создается определением» даже не вычисляется, поскольку определение является синтаксической конструкцией, а объект существует во время выполнения. Он мог бы сказать оценку, но я не уверен, что определения «оценены». Это плохой стандарт!
@curiousguy Вы заставляете меня переделывать себя до того, как меня сформировали, за счет того, что я много читал стандарт. Я думаю, что я принял это «экзистенциалистское» использование глагола «быть» для объектов (в стандарте, когда он написан ... есть объект. Это означает, что объект был построен, но не был уничтожен, и его хранилище не было освобождено). Но как бы то ни было, возможно иметь выражение, ссылочный тип которого является неполным: объект создается в единице перевода, где тип завершен, а затем упоминается в другой единице перевода, где тип является неполным, и в этом случае, как вы говорите,
"возможно выражение, ссылочный тип которого является неполным" Ясно, что да. По этому поводу не было разногласий.
Нет, вот контрпример
#include <iostream>
struct A
{
int a_member[10];
};
struct B : public virtual A
{
int b_member[10];
void print_b() { std::cout << static_cast<void*>(this) << " " << static_cast<void*>(std::addressof(this->a_member)) << " " << static_cast<void*>(this + 1) << std::endl; }
};
struct C : public virtual A
{
int c_member[10];
void print_c() { std::cout << static_cast<void*>(this) << " " << static_cast<void*>(std::addressof(this->a_member)) << " " << static_cast<void*>(this + 1) << std::endl; }
};
struct D : public B, public C
{
void print_d()
{
print_b();
print_c();
}
};
int main()
{
D d;
d.print_d();
}
С возможным выводом (как видел здесь)
0x7fffc6bf9fb0 0x7fffc6bfa010 0x7fffc6bfa008
0x7fffc6bf9fe0 0x7fffc6bfa010 0x7fffc6bfa038
Обратите внимание, что a_member находится за пределами B, на который указывает this в print_b.
И рассматривать такой базовый подобъект как элемент массива (как воображаемый массив из одного элемента) на самом деле не имеет смысла. Не уверен, что это должно быть законно.
Является ли data_member указателем?