Приведение между указателями примитивных типов

Четко определено следующее:

char* charPtr = new char[42];
int* intPtr = (int*)charPtr;

charPtr++;
intPtr = (int*) charPtr;

intPtr неправильно выровнен (по крайней мере, в одном из двух случаев). Это незаконно, просто иметь его там? Использует ли это UB на каком-либо этапе? Как можно его использовать, а как нельзя?


person Luchian Grigore    schedule 08.02.2013    source источник
comment
Если после этого вы попытаетесь использовать intPtr, произойдет невыровненный доступ (на соответствующих процессорах). Так что это определенно было бы неприемлемо. Хотя это может работать на некоторых процессорах - например, x86 с радостью, если будет немного медленнее, будет читать невыровненную память.   -  person Mats Petersson    schedule 08.02.2013
comment
Я думаю, что вы можете отбросить, но не можете использовать его после применения :) Я должен это проверить.   -  person Bartek Banachewicz    schedule 08.02.2013
comment
Что вы имеете в виду под «Можете ли вы его отбросить?»   -  person John Dibling    schedule 08.02.2013
comment
@JohnDibling удалил эту часть (выяснилось, что для работы требуется возврат назад).   -  person Luchian Grigore    schedule 08.02.2013
comment
@JohnDibling: Он имеет в виду, независимо от действительности intPtr, могу ли я хотя бы преобразовать intPtr в char* и повторно получить исходный действительный указатель для suresies?   -  person Lightness Races in Orbit    schedule 08.02.2013
comment
@JohnDibling: И ответ только в том случае, если T1 и T2 являются типами объектов и где требования выравнивания T2 не строже, чем требования T1, в противном случае это не указано ([C++11: 5.2.10/7]).   -  person Lightness Races in Orbit    schedule 08.02.2013
comment
В C++ лучше использовать static_cast вместо C стилей.   -  person Ivaylo Strandjev    schedule 08.02.2013
comment
@IvayloStrandjev stackoverflow.com/questions/2563045/   -  person Mysticial    schedule 08.02.2013
comment
@Mysticial Я не уверен, какой именно ответ вы имели в виду, но я вроде как согласен с этим сообщением   -  person Ivaylo Strandjev    schedule 08.02.2013
comment
@IvayloStrandjev, не слишком зацикливайтесь на самих актерах, вопрос не в этом.   -  person Luchian Grigore    schedule 08.02.2013
comment
@IvayloStrandjev За исключением того, что static_cast здесь незаконен. Ему придется использовать reinterpret_cast.   -  person James Kanze    schedule 08.02.2013


Ответы (2)


Во-первых, конечно: указатель гарантированно будет выровнен в первом случае (согласно §5.3.4 / 10 и §3.7.4.1 / 2) и может быть правильно выровнен в обоих случаях. (Очевидно, если sizeof(int) == 1, но даже если это не так, реализация не обязательно требует согласования.)

И чтобы прояснить ситуацию: все ваши броски reinterpret_cast.

Помимо этого, это интересный вопрос, потому что, насколько я могу судить, в отношении стандарта нет никакой разницы в двух приведениях. Результаты преобразования не указаны (согласно §5.2.10 / 7); у вас даже нет гарантии, что преобразование его обратно в char* приведет к исходному значению. (Очевидно, этого не произойдет, например, на машинах, где int* меньше char*.)

На практике, конечно: стандарт требует, чтобы возвращаемое значение new char[N] было достаточно выровнено для любого значения, которое может в него вписаться, поэтому вы гарантированно сможете:

intPtr = new (charPtr) int;

Это имеет точно такой же эффект, как и ваш приведение, учитывая, что конструктор по умолчанию для int не работает. (И при условии, что sizeof(int) <= 42.) Так что трудно представить реализацию, в которой первая часть не работает. У вас должна быть возможность использовать intPtr, как и любой другой intPtr, полученный легальным путем. И идея о том, что преобразование его обратно в char* каким-то образом приведет к отличному от исходного char* значению, кажется абсурдной.

Во второй части все ставки отключены: вы определенно не можете разыменовать указатель (если ваша реализация не гарантирует иное), и также вполне возможно, что преобразование его обратно в char* приведет к чему-то другому. (Представьте, например, машину, адресуемую по слову, в которой преобразование char* в int* округляется в большую сторону. Тогда обратное преобразование приведет к char*, который был на sizeof(int) выше исходного. Или когда попытка преобразовать неверно выровненный указатель всегда приводила к нулю указатель.)

person James Kanze    schedule 08.02.2013

Как правило, результат не указан (5.2.10p7), если требования к выравниванию int больше, чем требования char, которые обычно и будут. Результатом будет допустимое значение типа int *, поэтому оно может быть, например, печатается как указатель с operator<< или преобразуется в intptr_t.

Поскольку результат имеет неопределенное значение, если это не указано в реализации, это неопределенное поведение, чтобы косвенно использовать его и выполнять преобразование lvalue-to-rvalue для результирующего int lvalue (кроме неоцененных контекстов). Обратное преобразование в char * не обязательно будет происходить в оба конца.

Однако, если исходный char * сам был результатом преобразования из int *, то преобразование в int * считается второй половиной цикла туда и обратно; в этом случае приведение определяется.

В частности, в приведенном выше случае, когда char * был результатом выражения new[], мы гарантируем (5.3.4p10), что указатель char * правильно выровнен для int, пока sizeof(int) <= 42. Поскольку выражение new[] получает память от функции распределения, применяется 3.7.4.1p2; указатель void * может быть преобразован в указатель любого полного типа объекта с фундаментальным требованием выравнивания, а затем использован для доступа к объекту [...], что настоятельно подразумевает, наряду с примечанием к 5.3.4p10 , что то же самое касается указателя char *, возвращаемого выражением new[]. В этом случае int * является указателем на неинициализированный объект int, поэтому выполнение преобразования lvalue-to-rvalue для его косвенного обращения не определено (3.8p6), но присвоение его косвенному обращению полностью определено. Объект int в выделенном хранилище (3.7.4.1p2), поэтому преобразование int * обратно в char * даст исходное значение для 1.8p6. Это не выполняется для увеличенного указателя char *, поскольку, если sizeof(int) == 1 не является адресом объекта int.

person ecatmur    schedule 08.02.2013
comment
Оригинальный char* был результатом new char[42]. - person Lightness Races in Orbit; 08.02.2013