2014-11-25 1 views
2

Рассмотрим следующую программу:Cast указатель на структуру к указателю единственным членом этой структуры

#include <algorithm> 
#include <iostream> 
#include <vector> 

struct foo { 
    foo(int value) 
    : value_(value) 
    { 
     // perform range checks 
    } 

    int value() const { 
     return value_; 
    } 

private: 
    int value_; 
}; 

int main() { 
    std::vector<foo> values{0, 1, 2, 3, 4, 5}; 

    std::for_each(std::begin(values), std::end(values), 
        [](foo& f){ std::cout << f.value(); }); 

    std::cout << std::endl; 

    std::for_each(reinterpret_cast<const int*>(values.data()), 
        reinterpret_cast<const int*>(values.data()) + values.size(), 
        [](int i){ std::cout << i; }); 
} 

После компиляции с Apple, LLVM версии 6.0 (лязг-600.0.54) (на основе LLVM 3.5 СВН), то получается следующий результат (который является именно то, что я хочу):



Первая итерация тривиальна. Однако вторая итерация выполняется не через итераторы, а через указатели на базовое хранилище, которые были отлиты до const int*.

Мой вопрос: Является ли этот код легальным?

Моя интуиция такова, что она есть. В соответствии с §5.2.10/7 стандарта C++ 11 (окончательный рабочий проект):

Когда prvalue v типа «указатель на T1» преобразуется к типу «указатель на сортаT2», то результат будет static_cast<cvT2*>(static_cast<cvvoid*>(v)), если оба T1 и T2 являются типа стандартной компоновки (3.9) и требования к расстановке T2 не не строже, чем у T1

Если I интерпретировать это правильно, то код выше должен быть правильным, не так ли? Если нет, можно ли заставить его работать?

+2

уверен в этом: это не нарушает строгое сглаживание (так как у структур есть объект «double» в качестве исходного элемента). Это напоминает мне требование 'std :: complex ', в соответствии с чем его адрес должен быть конвертируемым в первый элемент массива из двух 'T's (i. E.' T * '). Поэтому я предполагаю, что в этом случае закрытие отступов не допускается, и вы будете в безопасности. –

+0

Спасибо, я думаю, что это вполне может быть ответом. –

+0

:) Я так не думаю - просто потому, что у меня нет стандартных доказательств, подтверждающих это ... просто пример. что это прекрасно, но это не гарантирует :) –

ответ

5

(В моем ответе я использую C++ 14 стандартный проект (N4140), который немного отличается от C++ 11 с учетом соответствующих цитат)

reinterpret_cast<const int*>(values.data()) является штраф из-за [class.mem]/19:

Если объект класса стандартной компоновки имеет какие-либо не-статические элементы данных, его адрес совпадает с адресом его первым нестатического элементом данных. (...) [Примечание. Таким образом, в рамках объекта структуры стандартного макета может быть неназванное заполнение, но не в начале, по мере необходимости, для достижения соответствующего выравнивания.-end примечание]

А что касается разыменования, [expr.reinterpret.cast]/7:

указатель на объект может быть явно преобразован в указатель на объект другого типа. Когда prvalue v типа указателя объекта преобразуется в тип указателя объекта «указатель на cv T», результатом является static_cast<cv T*>(static_cast<cv void*>(v)).

Первого static_cast покрыт [conv.ptr]/2:

prvalue типа «указатель на сорт T», где T является типом объекта, может быть преобразован в prvalue типа «указатель на сорта void ". Результат преобразования ненулевого значения указателя на тип объекта на «указатель на cv void» представляет адрес того же байта в памяти, что и исходное значение указателя.

Второй static_cast - [expr.static.cast]/13:

prvalue типа «указатель на CV1 пустоте» может быть преобразовано в prvalue типа «указатель на CV2 Т» (...) Если оригинал значение указателя представляет собой адрес а байт в памяти и а удовлетворяет требование выравнивания T, то полученное значение указателя представляет один и тот же адрес, что и исходное значение указателя, то есть, A.

требование выравнивания выполнено потому как от [class.mem]/19, поэтому литье отлично работает.


Но проблема в том, что, кажется, нет никакой гарантии, что sizeof(foo) == sizeof(int) кроме вышеупомянутого требования к std::complex. Можно интерпретировать заметку о неназванном дополнении от [class.mem]/19 как разрешение заполнения только в том случае, если это необходимо для выравнивания, поэтому в вашем случае не должно быть никаких дополнений, но, на мой взгляд, эта заметка слишком расплывчата в этом отношении.

Что вы можете сделать, это положить в код

static_assert(sizeof(foo) == sizeof(int), ""); 
// this may be paranoic but won't hurt 
static_assert(alignof(foo) == alignof(int), ""); 

Так, по крайней мере ваш код не будет компилироваться, если эти требования нарушаются.

+0

Удивительный ответ, спасибо! –

2

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

Это указано в §9.2/18 [class.mem]:

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

+0

Вот чего я ожидал. Фактически, прецедент для этого должен быть совместим с C API. –

+0

Уход за стандартным текстом, поддерживающим то, что вы сказали? –

+0

@Puppy: После некоторого дополнительного копания я нашел соответствующую стандартную цитату и добавил ее к вашему ответу. Надеюсь, с тобой все в порядке. –

Смежные вопросы