Сопоставление массивов с помощью структур

Я читаю параграф 7 из 6.5 стандарта ISO / IEC 9899: TC2.

Он оправдывает доступ lvalue к объекту посредством:

тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегата или содержащегося объединения),

Пожалуйста, обратитесь к документу, чтобы узнать, что такое «вышеупомянутые» типы, но они, безусловно, включают эффективный тип объекта.

Он находится в разделе, отмеченном как:

Назначение этого списка - указать те обстоятельства, при которых объект может иметь или не иметь псевдоним.

Я прочитал это как высказывание (например) о том, что следующее четко определено:

#include <stdlib.h>
#include <stdio.h>

typedef struct {
    unsigned int x;
} s;

int main(void){
    unsigned int array[3] = {73,74,75};

   s* sp=(s*)&array; 

   sp->x=80;

   printf("%d\n",array[0]);

   return EXIT_SUCCESS;
}

Эта программа должна вывести 80.

Я не защищаю это как хорошую (или очень полезную) идею и признаю, что частично интерпретирую это таким образом, потому что я не могу понять, что еще это означает, и не могу поверить, что это бессмысленное предложение!

Тем не менее, я не вижу очень веской причины запретить это. Что мы действительно знаем, так это то, что выравнивание и содержимое памяти в этом месте совместимы с sp->x, так почему бы и нет?

Кажется, доходит до того, что я могу сказать, что если я добавлю (скажем) double y; в конец структуры, я все равно смогу получить доступ к array[0] через sp->x таким образом.

Однако даже если массив больше sizeof(s), любая попытка доступа к sp->y является неопределенным поведением «все ставки отключены».

Могу я вежливо попросить людей сказать то, что оправдывает это предложение, вместо того, чтобы пуститься в тупик, крича «строгое сглаживание UB строгое сглаживание UB», как это, кажется, слишком часто бывает в подобных вещах.


person Persixty    schedule 12.01.2015    source источник
comment
Нарушение строгих правил псевдонима может привести к тому, что сильно оптимизирующие компиляторы будут генерировать код, который не выполняет то, что вы намереваетесь. Я видел, как операции чтения / записи переупорядочивались (с помощью указателей он думал, что не может указывать на один и тот же объект, но делал это), в результате чего назначения, казалось бы, пропускались.   -  person Jonathon Reinhart    schedule 12.01.2015
comment
Я это прекрасно знаю. Этот параграф явно пытается объявить, какие псевдонимы компиляторы должны и не должны уважать. Что происходит, когда вы просите свой компилятор проигнорировать некоторые из более осторожных правил и поехать в город, выходит за рамки этого вопроса.   -  person Persixty    schedule 12.01.2015
comment
Не просматривая снова справочную документацию, если я правильно помню, на самом деле это означало ссылаться на экземпляры, в которых массив был членом самой структуры, а не просто был типом, приведенным к указателю типа структуры. Поэтому, если вы хотите передать массив по значению, поместите его как элемент данных в структуру и передайте структуру по ссылке   -  person fkl    schedule 12.01.2015
comment
@fayyazkl Не думаю, что это подходит. В абзаце говорится об объектах, к которым осуществляется доступ. Каждый член массива является объектом. Итак, я допускаю, что если у вас есть член массива и вы «выровняли его» над массивом, то в этом абзаце (согласно моей предложенной интерпретации) говорится, что к нему можно получить доступ как к lvalue. Но это частный случай того, что говорится в этом абзаце.   -  person Persixty    schedule 12.01.2015
comment
Вы прочитали и усвоили что такое строгое правило псевдонима? и указанные ссылки оттуда? Они помогают?   -  person Jonathan Leffler    schedule 12.01.2015
comment
@JonathanLeffler Я хорошо знаю, что такое строгий псевдоним, почему он важен и как он может помочь или помешать вашей жизни. Я достаточно взрослый, чтобы использовать Borland Turbo C ++, когда вам, по сути, приходилось выбирать `` без псевдонима указателя '', чтобы получить достойный выход из наиболее трудоемких методов численного анализа (много больших массивов double), потому что старый плохой оптимизатор не мог выяснить что-нибудь с потенциально десятками очевидных псевдонимов, которых нет. Остается вопрос, что именно этот пункт разрешает при строгом соблюдении.   -  person Persixty    schedule 12.01.2015
comment
@JonathanLeffler Итак, я хочу сказать, что означает выделенный мною абзац, если это не означает, что принятый ответ, который вам нравится, неверен, если на вашей платформе unsigned int 32-битный. Поскольку (как выясняется) пункт. Я подчеркиваю, что изо всех сил явно разрешает такой вид псевдонимов. Кстати, я думаю, что это довольно эзотический вопрос, и чистая двусмысленность этого утверждения заставляет меня неохотно полагаться на него. Кто знает, правильно ли его интерпретируют разработчики? Но остается вопрос. О чем говорится в этом конкретном предложении? Нет, пожалуйста, объясните мне псевдоним. Я в порядке.   -  person Persixty    schedule 12.01.2015
comment
Применяется ли ISO / IEC 9899: 1999 с TC2? Или это 9899: 2011 с примененным TC2? Я не понимаю, что 9899: 1999 TC2 на самом деле изменяет спецификацию на этом этапе, и номера абзацев в них одинаковы, а содержание, по крайней мере, очень похоже (я собираюсь начать их дословную проверку). Пункт 6 определяет «эффективный тип». Раздел 6.2.7 как в 9899: 1999, так и в 9899: 2011 определяет «совместимый тип». Было бы проще, если бы я точно знал, о каком стандарте вы говорите, и если вас беспокоит C99, почему не C11? (Я не знаю TC2 для C11, поэтому предполагаю, что это C99.)   -  person Jonathan Leffler    schedule 13.01.2015
comment
Обратите внимание, поскольку есть удаленный ответ, в котором упоминаются gcc и -fstrict-aliasing. В gcc документах говорится, что что все уровни имеют разную степень ложных срабатываний и отрицаний и поэтому не могут использоваться в качестве надежных указание на то, что код не нарушает строгого псевдонима. Проверка терпит неудачу на многих тривиальных примерах.   -  person Shafik Yaghmour    schedule 13.01.2015
comment
@JonathanLeffler Во избежание сомнений этот документ: open- std.org/jtc1/sc22/wg14/www/docs/n1124.pdf, хотя эта фраза встречается и в других стандартах, включая, по крайней мере, некоторые (возможно, все) стандарты C ++. Это попытка выразить то, что некоторые люди считают важным. Но что?   -  person Persixty    schedule 13.01.2015
comment
Читая различные страницы и сообщения об этом, кажется, существует распространенное заблуждение, что если X * и Y * (несовместимые) указывают на перекрывающиеся области памяти, то они не могут быть оба использованы для доступа к какому-либо подобъекту. Однако мне кажется, что из формулировки, выбранной стандартами, очень ясно, что если X и Y оба содержат член одного и того же типа, то доступ к этому члену через X и через Y не является нарушением псевдонима.   -  person M.M    schedule 13.01.2015
comment
@MattMcNabb Пока что сформулировано лучшее изложение ситуации. Я по-прежнему не могу интерпретировать это предложение как иное, чем явно разрешающее то, что вы говорите, - доступ к объекту того же типа, что и члены, через агрегаты, которые (в целом) не обязательно совместимы. Если вы опубликуете это как ответ, вполне возможно, что я в конечном итоге его приму.   -  person Persixty    schedule 13.01.2015
comment
Изначально я очень скептически относился к тому, что код может соответствовать коду, но, по-видимому, это так, хотя на первый взгляд ненамеренно. Я узнал кое-что новое из этого вопроса, и оказалось, что это действительно отличный вопрос. cc @JonathonReinhart   -  person Shafik Yaghmour    schedule 13.01.2015
comment
@JonathanLeffler, этот вопрос о строгом псевдониме не касается этого случая, это очень странный случай и, по-видимому, известная проблема.   -  person Shafik Yaghmour    schedule 13.01.2015
comment
@ShafikYaghmour Не думаю, что это так уж странно. Если вы перечитаете тот отчет о дефектах, который вы процитировали, вы увидите, что он, возможно, охвачен другими пунктами (хотя и не ясно). На карту поставлено то, что действительно ли структуры представляют собой (дополненную) сумму их членов, или члены в структурах могут (и / или не могут) использоваться способами, эквивалентными объектам, которые не являются членами, могут (и / или нельзя) использовать? Ответом на этот вопрос действительно должно быть выразительное выражение: структуры на самом деле представляют собой дополненную сумму своих частей (не больше и не меньше), и если это не так, вы начинаете побеждать сильные стороны языка C.   -  person Persixty    schedule 13.01.2015
comment
@ShafikYaghmour Я должен это опубликовать. Очевидно, что мемуары Денниса Ричи (RIP) не являются нормативными, но это предложение глубоко напоминает проблему, в которой ранний C не привязывал структурные указатели жестко к структурам, на которые они указывали, и позволял программистам писать член-указатель почти безотносительно к тип указателя; такое выражение было некритически воспринято как ссылка на область памяти, обозначенную указателем, в то время как имя члена указывало только смещение и тип. (cm.bell-labs.com/who/dmr/chist.html). Удивительно, насколько глубок этот вопрос.   -  person Persixty    schedule 13.01.2015
comment
@DanAllen, когда я говорю odd, я имею в виду нарушает интуицию разработчиков в целом. Я бы не стал считать проблемы строгого алиасинга общеизвестными, и этот конкретный случай не охвачен ни одной известной мне статьей о строгом алиасинге, так что это довольно особый случай.   -  person Shafik Yaghmour    schedule 13.01.2015
comment
@ShafikYaghmour Мы со всем согласны. Это довольно эзотерический момент, но чем больше я смотрю на него, тем больше понимаю, что это важный момент. Я согласен, что это далеко не так хорошо понятно. Отчасти я виню то, что кажется поспешно написанным и плохо изученным разделом стандарта.   -  person Persixty    schedule 13.01.2015
comment
@DanAllen: В то время, когда был написан Стандарт, язык C начал процветать, несмотря на то, что все, что делала любая программа C, с точки зрения Стандарта, было неопределенным поведением (стандарт, который еще не существовал, мог не ни к чему не предъявляю требований). Если поведение, очевидно, было бы полезно на некоторых платформах, но не на других, оставление поведения как Undefined должно было просто сохранить статус-кво. К сожалению, стало модно относиться к Стандарту как к неопределенным вещам, которые качественные реализации считали бы определенными ранее.   -  person supercat    schedule 06.08.2016


Ответы (3)


Ответ на этот вопрос содержится в предложении: Исправление правила для псевдонимов на основе типов, которые мы увидим, к сожалению, не были решены в 2010 году, когда было внесено предложение, описанное в Хедквист, Батива, ноябрь 2010 г., протоколы. Следовательно, C11 не содержит разрешения N1520, поэтому это открытый вопрос:

Похоже, что на этой встрече этот вопрос не решится. Каждая ветка предлагаемых подходов вызывает больше вопросов. 1 1:48, чт, 4 ноября 2010 г.

ДЕЙСТВИЕ - Кларк больше поработает в

N1520 начинает говорить (акцент мой на будущее):

Ричард Хансен указал на проблему в правилах псевдонимов на основе типов, а именно:

Мой вопрос касается формулировки пункта 5 из 6.5p7 (псевдонимы применительно к объединениям / агрегатам). Если мое понимание эффективного типа неверно, похоже, что условие объединения / агрегирования должно применяться к эффективному типу, а не к типу lvalue.

Вот еще некоторые подробности:

В качестве примера возьмем следующий фрагмент кода:

union {int a; double b;} u;
u.a = 5;

Насколько я понимаю определение эффективного типа (6.5p6), эффективным типом объекта в местоположении & u является union {int a; двойной б;}. Тип выражения lvalue, которое обращается к объекту в & u (во второй строке), - int.

Насколько я понимаю определение совместимого типа (6.2.7), int несовместимо с union {int a; double b;}, поэтому пункты 1 и 2 из 6.5p7 не применяются. int не является типом типа union со знаком или без знака, поэтому пункты 3 и 4 не применяются. int не является символьным типом, поэтому пункт 6 не применяется.

Остается bullet 5. Однако int не является агрегатным или объединенным типом, поэтому bullet также не применяется. Это означает, что приведенный выше код нарушает правило псевдонима, чего, очевидно, не должно быть.

Я считаю, что пункт 5 следует перефразировать, чтобы указать, что если эффективный тип (не тип lvalue) является агрегатным или объединенным типом, который содержит член с типом, совместимым с типом lvalue, то к объекту можно получить доступ.

По сути, он указывает на то, что правила асимметричны в отношении членства в структурах / объединениях. Я знал об этой ситуации и довольно долгое время считал ее (несрочной) проблемой. Серия примеров лучше проиллюстрирует проблему. (Эти примеры изначально были представлены на встрече в Санта-Крус.)

По моему опыту с вопросами о допустимости псевдонимов на основе ограничений типа, вопрос неизменно формулируется в терминах инвариантности цикла. Такие примеры делают проблему очень острой.

И соответствующий пример, применимый к этой ситуации, будет 3, который выглядит следующим образом:

struct S { int a, b; };
void f3(int *pi, struct S *ps1, struct S const *ps2)
{
  for (*pi = 0; *pi < 10; ++*pi) {
      *ps1++ = *ps2;
  }
}

Вопрос здесь в том, можно ли получить доступ к объекту * ps2 (и особенно изменить) путем присвоения lvalue * pi - и если да, то действительно ли стандарт так говорит. Можно было бы возразить, что это не покрывается пятым пунктом 6.5p7, поскольку * pi вообще не имеет агрегатного типа.

** Возможно, намерение состоит в том, чтобы решить вопрос: разрешен ли доступ к значению объекта pi с помощью lvalue ps2. Очевидно, этот случай подпадет под пятый пункт.

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

Следующее обсуждение и предлагаемые решения очень длинные и их сложно резюмировать, но, похоже, они заканчиваются удалением вышеупомянутого пункта 5 и решением проблемы с корректировкой других частей 6.5. Но, как отмечалось выше, эти проблемы не были решаемы, и я не вижу дальнейших предложений.

Таким образом, может показаться, что стандартная формулировка допускает сценарий, который демонстрирует ОП, хотя я понимаю, что это было непреднамеренно, и поэтому я бы избегал этого, и он потенциально может измениться в более поздних стандартах, чтобы не соответствовать.

person Shafik Yaghmour    schedule 13.01.2015
comment
Огромное спасибо Шафику. Частично эти дебаты указывают на неспособность охватить поведение профсоюзов в разделе, аннотированном, как предназначенный для того, чтобы сказать, что является алиасингом, а что нет. Я думаю, это могло быть задумано как симметрия. Вы можете вырезать указатели на такие члены, как & (s.a). Так почему бы не наклеить структуру на совместимые элементы? Они (логически) выровнены и содержат совместимые битовые шаблоны. Почему язык низкого уровня, такой как C, должен запрещать такой доступ? Я все еще думаю, что это имел в виду автор этого абзаца. Интересно, кто это прислал. - person Persixty; 13.01.2015
comment
PS: Полностью согласен, это не рекомендуемый метод хотя бы потому, что он расплывчатый, явно малоизвестный и еще менее понятный! Я могу представить себе некоторый процесс, в котором умная функция ввода-вывода «строит» структуру рецепта на основе типов и смещений, а затем возвращает их вызывающей программе. Вам нужно будет полагаться на идею, что структуры можно рассматривать как части (и это говорит о том, что они состоят из частей), чтобы такая схема была совместимой. Я действительно думаю, что тот факт, что я реализовал структуру только наполовину (если вы добавили двойной y;), может быть непреднамеренным. Кажется, у него не так много законных целей. - person Persixty; 13.01.2015
comment
В этом ответе (особенно в примере 3) действительно говорится о псевдониме int через тип структуры, который имеет int в качестве члена. Однако код OP только псевдонимы uint как uint. Поэтому я думаю, что вопрос OP остается без ответа. (sp->x явно не имеет доступа ко всем *sp) - person M.M; 13.01.2015
comment
Для @ m.m bullet 5 требуется includes one of the aforementioned types among itsmembers, который применяется в коде OPs и является примером случая, который охватывает 3, и я не вижу в предложении ничего, что препятствовало бы его применению в этом случае. - person Shafik Yaghmour; 13.01.2015
comment
@ShafikYaghmour Я принял ваш ответ как очень полезный вывод, что предыдущее подробное рассмотрение этого абзаца, по крайней мере, обсуждает сами проблемы в игре. В том числе и то, что псевдонимы появляются как обсуждение системы типов, но выходят за рамки обсуждения оптимизации! Что я считаю важным - иначе ответ был бы легким! Ниже я попытаюсь показать, почему стоит разрешить что-то хотя бы немного похожее на OP. Еще раз спасибо за внимание. - person Persixty; 13.01.2015
comment
@DanAllen: да, оптимизация - это ключевая проблема, даже если язык поддерживает код, мне неясно, работают ли различные реализации компилятора с таким кодом. Простые примеры не показывают никакой оптимизации, но я бы не стал на это рассчитывать. - person Shafik Yaghmour; 13.01.2015
comment
@ShafikYaghmour снова. Я согласен. Не существует закона вселенной, чтобы оптимизаторы понимали этот раздел таким же образом. Вот почему он такой плохой. Он не может прояснить то, что он должен прояснить для той цели, которую он пытался прояснить. Извините, автор неизвестен, но проголосовали против с 2015 по 1999 год. - person Persixty; 13.01.2015
comment
В цитируемом тексте игнорируется тот факт, что у структур есть подобъекты. В последнем примере эффективным типом *ps1 является struct S, однако эффективным типом ps1->a является (или должно быть, ИМО) int, а не struct S - person M.M; 04.03.2015
comment
В Стандарте неясно, что такое подобъекты в разных местах (еще одно - это границы указателей на 1-мерный подобъект двумерного массива). - person M.M; 04.03.2015
comment
@ShafikYaghmour: Сколько возможностей оптимизации было бы потеряно, если бы правила псевдонима на основе типов были устранены, и программистам пришлось бы использовать ограничение, чтобы сказать, что будет, а что не будет псевдонимом? Насколько я могу судить, если у правил была какая-то причина для существования в C89, они, по сути, испарились с C99. Мне кажется странным, что некоторые компиляторы становятся все более агрессивными с оптимизацией на основе типов, поскольку места, где программисты не используют ограничение, скорее всего, являются теми, где скорость меньше всего беспокоит. - person supercat; 03.11.2015
comment
Где вы говорите DR 1520, я думаю, вы имеете в виду N1520 - person M.M; 08.08.2018
comment
Если я правильно помню, до C99 Комитет указывал, что они рассматривают это как вопрос качества реализации, и проблема никогда не будет решена, пока она не будет признана таковой. У разных типов программ разные семантические требования, и компромиссы между семантикой и производительностью, подходящие для одних, не подходят для других. Я думаю, что Стандарт должен признавать более двух типов реализаций, и может иметь смысл установить точные правила для некоторых из них ... - person supercat; 14.08.2018
comment
... но любой единый набор правил будет либо препятствовать тому, что должно быть полезным оптимизациям, либо делать язык полезным для многих целей (за исключением реализаций, которые по крайней мере отказываются от них), либо и то, и другое. Я не думаю, что было бы сложно поддерживать 95% + кода, который в настоящее время требует -fno-strict-aliasing, при этом позволяя большинство полезных оптимизаций, которые будут заблокированы флажком, но Стандарт также должен распознавать реализации, которые поддерживают еще более широкий спектр программ и тех которые поддерживают меньше программ, но обеспечивают большую оптимизацию. - person supercat; 14.08.2018

Я думаю, что этот текст неприменим:

агрегатный тип или тип объединения, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегатного или содержащегося объединения),

sp->x имеет тип unsigned int, который не является типом агрегирования или объединения.

В вашем коде нет строгого нарушения псевдонима: можно читать unsigned int как unsigned int.

У структуры могут быть другие требования к выравниванию массива, но в остальном проблем нет.

Доступ через «агрегатный или объединенный тип» будет:

s t = *sp;
person M.M    schedule 12.01.2015
comment
@JonathanLeffler Можно утверждать, что sp->x означает (*sp).x и, следовательно, сначала осуществляется доступ через агрегированный тип или тип объединения, а затем сужается доступ только до доступа x ... но в любом случае это не является строгим нарушением псевдонима - person M.M; 13.01.2015
comment
Чтобы различать эти два случая, s можно увеличить, чтобы на конце было несколько double или других предметов. - person M.M; 13.01.2015
comment
C11, Типы 6.2.5.28 запрещают. - person this; 13.01.2015
comment
@this в n1570, который является указателем на void, должен иметь те же требования к представлению и выравниванию ... что не связано - person M.M; 13.01.2015
comment
Все указатели на структурные типы должны иметь одинаковые требования к представлению и выравниванию. Все указатели на типы объединения должны иметь одинаковые требования к представлению и выравниванию. Указатели на другие типы не обязательно должны иметь такие же требования к представлению или выравниванию. - person this; 13.01.2015
comment
c не определяет, что указатель s может даже представлять указатель char. Совершенно допустимо иметь: sizeof (s *) == 2 и sizeof (unsigned int *) == 4 и / или различное выравнивание между ними. - person this; 13.01.2015
comment
Другая проблема заключается в том, что все еще имеет место строгое наложение. Вы интерпретируете массив int как структуру. Бум. Вы используете этот указатель на структуру для доступа к одному из его элементов, но это уже слишком поздно. - person this; 13.01.2015
comment
@ это не имеет ничего общего с этим вопросом, размеры могут быть такими, как вы говорите, и вопрос остается. Кажется, вы сомневаетесь в правильности выражения (s *)&array, однако это явно разрешено 6.3.2.3/7 - person M.M; 13.01.2015
comment
Если результирующий указатель неправильно выровнен68) для указанного типа, поведение не определено. В случае, если выравнивание другое, у вас есть ub; это могло произойти, если бы размеры были разными или нет. - person this; 13.01.2015
comment
@this unsigned int имеет псевдоним unsigned int, в этом примере нет даже массива символов, не говоря уже о том, который интерпретируется как структура. В любом случае, процитированный текст говорит, что строгое правило псевдонима позволяет интерпретировать вещи как структуру. - person M.M; 13.01.2015
comment
@ этот мой пост уже отмечает рассмотрение выравнивания. Он не определен только в том случае, если есть на самом деле нарушение выравнивания. Мы могли бы добавить некоторые alignof или другие проверки, чтобы обнаружить или исправить эту ситуацию, но это не имеет отношения к вопросу, который касается алиасинга (а не выравнивания). - person M.M; 13.01.2015
comment
Это была опечатка. Я имел в виду массив unsigned int, когда сказал char. - person this; 13.01.2015
comment
В любом случае, процитированный текст говорит, что строгое правило псевдонима позволяет интерпретировать вещи как структуру. Ммм, что это за текст ?? - person this; 13.01.2015
comment
тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегата или содержащегося объединения). Структуры - это агрегаты. - person M.M; 13.01.2015
comment
@MattMcNabb Конечно, это верно для члена структуры. Но сама структура вызывает строгое алиасинг. Вы не можете интерпретировать беззнаковое int * как структуру *. - person this; 13.01.2015
comment
@this s содержит unsigned int среди своих членов, поэтому разрешено использовать псевдоним array[0] как s - person M.M; 13.01.2015
comment
В любом случае нет псевдонимов в качестве структуры, sp->x имеет тип unsigned int. - person M.M; 13.01.2015
comment
@MattMcNabb sp не имеет типа unsigned int *. Строгое нарушение алиасинга. ooops ›››› * sp - person this; 13.01.2015
comment
@this *sp имеет тип s, и разрешено использовать s для псевдонима unsigned int - person M.M; 13.01.2015
comment
@MattMcNabb Вот в чем проблема, вы неправильно читаете стандарт. Этот пример ясно показывает, почему stackoverflow.com/questions / 98650 / и даже использует ту же настройку структуры. - person this; 13.01.2015
comment
@ это там черным по белому. Объект (array[0]) должен иметь доступ к своему сохраненному значению только с помощью выражения lvalue (sp->x), которое имеет один из следующих типов: - тип, совместимый с эффективным типом объекта. array[0] имеет тип unsigned int, а sp->x - тип unsigned int. Эти два типа совместимы, поэтому нет никаких нарушений. Нет необходимости даже рассматривать псевдонимы всей структуры, поскольку этот код этого не делает (хотя это также было бы разрешено). - person M.M; 13.01.2015
comment
@this в примере в принятом ответе unsigned int имеет псевдоним uint32_t, что может быть несовместимо. Если в образце использован _3 _..., то проблем нет. - person M.M; 13.01.2015
comment
Также существуют другие правила для памяти malloc'd, чем для именованных переменных (см. Термин эффективный тип) - person M.M; 13.01.2015
comment
Это агрегированный тип (ы), который включает один из вышеупомянутых типов (unsigned int). Это предложение нельзя читать, чтобы сказать, что член должен быть агрегатным типом. (Я думаю) ясно, что если член является агрегатным типом, то правило применяется рекурсивно. Я мог бы иметь struct {struct {struct {unsigned int x; }}} и все будет в порядке, но я определенно не обязан заниматься таким вложением. - person Persixty; 13.01.2015
comment
@this С одной стороны, я читаю не эту спецификацию. Но что еще более важно, пожалуйста, помните, что я спрашиваю, что означает процитированный мною абзац, а не является ли программа, которую я выдвинул, действительной. Соответствующий абзац появляется в доступном мне документе C11. Я думаю, что это появляется во многих стандартах после C99, когда псевдонимы (предположительно) были очищены. - person Persixty; 13.01.2015
comment
@this Я думаю, что пример в этом ответе проблематичен, в ответе есть несколько проблем, которые более чем легко исправить с помощью незначительных правок. - person Shafik Yaghmour; 13.01.2015
comment
Я думаю, было бы полезно сказать, что Стандарт сознательно воздерживается от утверждения, что все lvalue типов агрегированных членов должны использоваться для доступа к агрегатам при любых обстоятельствах. Вместо этого он ожидает, что реализации будут поддерживать шаблоны использования, выходящие за рамки требований стандарта, когда это имеет смысл для целевой платформы и области приложения. Другими словами, поддержка таких шаблонов - это вопрос качества реализации. - person supercat; 09.08.2018
comment
Реализация, которая могла бы обрабатывать aggregate.member только для членов символьного типа, была бы совершенно очевидно паршивой, независимо от того, действительно ли Стандарт требовал поддержки этой конструкции. Поскольку авторы Стандарта не считали необходимым или практичным запрещать все способы, которыми реализация могла бы быть настолько низкого качества, чтобы быть бесполезной, или беспокоиться о том, соответствуют ли явно паршивые реализации, не было реальной необходимости санкционировать поддержка aggregate.member. - person supercat; 09.08.2018

Признаюсь, идея о том, что я могу наложить struct на локально определенный массив таким образом, откровенно экзотична. Я все еще утверждаю, что C99 и все последующие стандарты это позволяют. На самом деле очень спорно, что члены, являющиеся объектами сами по себе, первый пункт в 6.7.5 допускает это:

тип, совместимый с эффективным типом объекта

Я думаю, что в этом дело М.М.

Взглянув на проблему с другой стороны, отметим, что абсолютно законно (в строго соответствующей среде) присвоить члену sp->x псевдоним как самостоятельный объект.

В контексте кода в моем OP рассмотрим функцию с прототипом void doit(int* ip,s* sp);, ожидается, что следующий вызов будет вести себя логически:

doit(&(sp->x),sp);

NB: логика программы может (конечно) вести себя не так, как хотелось бы. Например, если doit увеличивает sp->x, пока не превысит *ip, тогда возникает проблема! Однако то, что не допускается в совместимом компиляторе, - это искажение результата из-за артефактов из-за того, что оптимизатор игнорирует потенциал псевдонимов.

Я утверждаю, что C был бы еще слабее, если бы язык требовал от меня кодирования:

int temp=sp->x;
doit(&temp,sp);
sp->x=temp;

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

Очевидно, что жестко оптимизирующий (т.е. несовместимый) компилятор может создать полный хэш doit(), если он не распознает, что ip может быть псевдонимом члена в середине sp. Это не имеет отношения к этому обсуждению.

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

Абсолютно установлено, что sp->x в моем OP указывает на правильно выровненное место, содержащее действительный unsigned int.

Интеллектуальные проблемы заключаются в том, согласен ли компилятор / оптимизатор, что тогда это законный способ доступа к этому месту, или игнорируется как неопределенное поведение.

Как показывает doit() пример, совершенно очевидно, что структуру можно разбить и рассматривать как отдельные объекты, которые просто имеют особые отношения.

Этот вопрос, по-видимому, касается обстоятельств, когда набор членов, у которых случаются такие особые отношения, может иметь структуру, «наложенную на них».

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

Он работает именно этим способом - он строит элемент за элементом структуры, а затем обращается к нему через эту структуру.

Я подозреваю, что некоторые люди, возражающие против кода в ОП, более расслаблены по этому поводу. Во-первых, он работает с памятью, выделенной из свободного хранилища, как универсально выровненное хранилище «нетипизированное». Во-вторых, выстраивает целую конструкцию. В OP я указываю правила (по крайней мере, кажущиеся разрешенными), согласно которым вы можете выстраивать биты структуры, и пока вы только отменяете ссылки на эти биты, все в порядке.

Я в некоторой степени разделяю это отношение. Я думаю, что OP немного извращен, а язык растягивается в плохо написанном углу стандарта. Не то, чтобы надеть рубашку.

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

Однако я скажу, что что-то вроде этого - единственное, что я мог придумать, когда такой подход кажется целесообразным. Но с другой стороны, если вы не можете разделить данные и / или собрать их вместе, тогда вы быстро начнете ломать представление о том, что структуры C - это POD - возможно дополненная сумма их частей, ни больше, ни меньше.

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

typedef enum {
    is_int, is_double //NB:TODO: support more types but this is a toy.

} type_of;

//This function allocates and 'builds' an array based on a provided set of types, offsets and sizes.
//It's a stand-in for some function that (say) reads structures from a file and builds them according to a provided
//recipe. 
int buildarray(void**array,const type_of* types,const size_t* offsets,size_t mems,size_t sz,size_t count){
    const size_t asize=count*sz;
    char*const data=malloc(asize==0?1:asize);
    if(data==NULL){
        return 1;//Allocation failure.
    }
    int input=1;//Dummy...
    const char*end=data+asize;//One past end. Make const for safety!
    for(char*curr=data;curr<end;curr+=sz){
        for(size_t i=0;i<mems;++i){
            char*mem=curr+offsets[i];
            switch(types[i]){
                case is_int:
                    *((int*)mem)=input++;//Dummy...Populate from file...
                break;
                case is_double:
                    *((double*)mem)=((double)input)+((double)input)/10.0;//Dummy...Populate from file...
                    ++input;
                break;
                default:
                    free(data);//Better than returning an incomplete array. Should not leak even on error conditions.
                    return 2;//Invalid type!
            }
        }
    }
    if(array!=NULL){
        *array=data;
    }else{
        free(data);//Just for fun apparently...
    }
    return 0;
}

typedef struct {
    int a;
    int b;
    double c;
} S;

int main(void) {
    const type_of types[]={is_int,is_int,is_double};
    const size_t offsets[]={offsetof(S,a),offsetof(S,b),offsetof(S,c)};
    S* array=NULL;
    const size_t size=4;

    int err=buildarray((void **)&array,types,offsets,3,sizeof(S),size);
    if(err!=0){
        return EXIT_FAILURE;
    }
    for(size_t i=0;i<size;++i){
        printf("%zu: %d %d %f\n",i,array[i].a,array[i].b,array[i].c);
    }

    free(array);
    return EXIT_SUCCESS;
}

Я думаю, это интересное напряжение. C предназначен для того, чтобы быть языком высокого уровня низкого уровня и предоставлять программисту почти прямой доступ к машинным операциям и памяти. Это означает, что программист может удовлетворить любые требования аппаратных устройств и написать высокоэффективный код. Однако, если программисту предоставляется абсолютный контроль, такой как моя точка зрения о подходе к алиасингу «если он подходит, то все в порядке», тогда оптимизатор испортит свою игру. Как ни странно, чтобы вернуть оптимизатору дивиденды, стоит немного снизить производительность.

Раздел 6.5 стандарта C99 пытается (и безуспешно) установить эту границу.

person Persixty    schedule 13.01.2015
comment
Компилятору пришлось бы быть невероятно тупым, чтобы не разрешать доступ к несимвольному члену структуры через sp->m, независимо от того, действительно ли Стандарт требовал от него этого. Кроме того, в Обосновании Стандарта прямо признается, что тупой компилятор может соответствовать требованиям, но при этом иметь такое низкое качество, что становится бесполезным. Собирая их вместе, вместо того, чтобы размышлять о том, как N1570 6.5p7 позволяет то, что, очевидно, должно, я думаю, имеет больше смысла признать, что в некоторых случаях это не так, но достойные компиляторы будут вести себя так, как будто они все равно поступают. - person supercat; 09.08.2018