Признаюсь, идея о том, что я могу наложить 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
gcc
и-fstrict-aliasing
. Вgcc
документах говорится, что что все уровни имеют разную степень ложных срабатываний и отрицаний и поэтому не могут использоваться в качестве надежных указание на то, что код не нарушает строгого псевдонима. Проверка терпит неудачу на многих тривиальных примерах. - person Shafik Yaghmour   schedule 13.01.2015X *
иY *
(несовместимые) указывают на перекрывающиеся области памяти, то они не могут быть оба использованы для доступа к какому-либо подобъекту. Однако мне кажется, что из формулировки, выбранной стандартами, очень ясно, что еслиX
иY
оба содержат член одного и того же типа, то доступ к этому члену черезX
и черезY
не является нарушением псевдонима. - person M.M   schedule 13.01.2015