2015-01-12 2 views
5

Я читаю параграф 7 из 6.5 в ISO/IEC 9899: TC2.Смещение массивов через структуры

Это потворствует именующий доступ к объекту посредством:

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

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

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

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

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

#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», похоже, слишком часто является способом этих вещей.

+3

Нарушение правил строгой сглаживания может привести к тому, что компиляторы с высокой степенью оптимизации будут генерировать код, который не сделает то, что вы намереваетесь. Я видел, что чтение/запись переупорядочено (через указатели, которые, по его мнению, не могли * указать на один и тот же объект, но сделали), вызывая, по-видимому, присваивание «пропущенных». –

+0

Мне это хорошо известно. Этот параграф явно пытается объявить, что должно быть соблюдено и не должно соблюдаться сглаживание строго совместимых компиляторов. Что происходит, когда вы просите своего компилятора игнорировать некоторые из более осторожных правил и отправляться в город, выходит за рамки этого вопроса. – Persixty

+0

Не переходя через ссылочный документ еще раз, если я правильно помню, это действительно означало обращение к экземплярам, ​​в которых массив был членом самой структуры, а не просто приписывал тип указателю типа struct. Поэтому, если вы хотите передать массив по значению, поместите его как элемент данных в структуру и передайте структуру по ссылке – fayyazkl

ответ

6

Ответ на этот вопрос кроется в предложении: Fixing the rules for type-based aliasing который мы видим, к сожалению, не был решен в 2010 году, когда было сделано предложение который включен в Hedquist, Bativa, November 2010 minutes. Поэтому C11 не содержит разрешение на DR 1520, так это открытый вопрос:

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

ACTION - Кларк делать больше работы в

DR 1520 открывает говоря (курсив мой идти вперед):

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

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

Вот еще некоторые детали:

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

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

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

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

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

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

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

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

И соответствующий пример, который относится к этой ситуации будет 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. Очевидно, что этот случай будет покрыт пятой пулей.

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

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

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

+0

Массивное спасибо Шафику. Часть этих дискуссий указывает на неспособность охватить поведение профсоюзов в секции, аннотированной, как предполагалось, чтобы сказать, что такое наложение и не соответствует. Я думаю, что это может быть сделано как симметрия. Вы можете вырезать указатели на элементы, такие как & (s.a). Так почему бы не придерживаться структуры над совместимыми членами? Они (логически) выровнены и содержат совместимые битовые шаблоны. Почему язык с низким уровнем, например C, запрещает такой доступ? Я все еще думаю, что это то, что имел в виду автор этого параграфа. Интересно, кто его подарил. – Persixty

+0

PS: Полностью согласен, что это не рекомендуемая техника, хотя бы потому, что она неопределенная, явно мало известная и даже менее понятная! Я могу представить себе какой-то процесс, когда умная функция ввода-вывода «строит» структуру рецепта на основе типов и смещений, а затем передает их обратно вызывающей программе. Вам нужно будет полагаться на эту идею, что структуры могут рассматриваться как части (и это говорит о том, что они созданы из частей), чтобы такая схема была совместимой.Я действительно думаю, что факт, что я только половину реализовал структуру (если вы добавите double y;), может быть непреднамеренным. Кажется, что у него много законных применений. – Persixty

+1

Этот ответ (особенно пример 3) действительно говорит о сглаживании 'int' через тип структуры, который имеет' int' в качестве члена. Однако код OP только псевдонимы 'uint' как' uint'. Поэтому я думаю, что вопрос ОФ остается без ответа. ('sp-> x' явно не имеет доступа ко всем' * sp') –

-1

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

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

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

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

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

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

s t = *sp; 
+0

@JonathanLeffler Можно утверждать, что 'sp-> x' означает' (* sp) .x', и поэтому он сначала запускается через совокупность или тип объединения, а затем сужает доступ только к 'x' ... но в любом случае это не является строгим нарушением псевдонимов. –

+0

Чтобы отличить эти два случая, 's' можно было увеличить, чтобы иметь некоторые' двойные или другие позиции на конце. –

+0

C11, типы 6.2.5.28 говорят, что нет-go. – this

0

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

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

Я думаю, что Мэтт McNabb-х точка.

Рассматривая проблему другим способом, отметим, что абсолютно законно (в строго соответствующей среде) псевдоним участника 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; 

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

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

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

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

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

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

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

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

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

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

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

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

Однако я скажу, что что-то вроде этого - единственное, что я мог придумать, где такой подход кажется целесообразным. Но, с другой стороны, если вы не можете разделить данные AND/OR, соедините их, тогда вы быстро начнете нарушать понятие в структурах 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 пытается (и не полностью преуспевает), чтобы установить эту границу.