2016-07-19 2 views
1

Является ли следующий правильный код?Использование выделенного пространства для хранения нескольких массивов

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

// allocate some space to A, and set *A and **A to different regions of that space 
char*** A = malloc(92); 
*A = (char**)((char*)A + 2*sizeof(char**)); 
**A = (char*)*A + 4*sizeof(char*); 

// initialize the second char** object 
A[1] = *A + 2; 

// write four strings further out in the space 
strcpy(A[0][0],"string-0-0"); 
A[0][1] = A[0][0] + strlen(A[0][0]) + 1; 
strcpy(A[0][1],"string-0-1"); 
A[1][0] = A[0][1] + strlen(A[0][1]) + 1; 
strcpy(A[1][0],"string-1-0"); 
A[1][1] = A[1][0] + strlen(A[1][0]) + 1; 
strcpy(A[1][1],"string-1-1"); 

Я нахожу такие вещи, как это полезно в тех случаях, когда это может быть не просто, как освободить объект. Например, скажем, A [1] [1] может перенаправляться или перенаправляться на адрес строкового литерала. В любом случае вы просто освободите A. Также количество запросов на malloc минимизировано.

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

7.22.3 Функции управления памятью

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

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

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

Хотя выше показывает, как представляется, нет явного нарушения стандарта, стандарт не явно разрешить поведение либо, и мы имеем пункт 2 главы 4 (курсив мой):

Если Требование '' должно '' или '' не должно '', которое появляется за пределами ограничения или ограничения времени выполнения, поведение не определено. Неопределенное поведение иначе указано в этом Международном стандарте словами «неопределенное поведение» или отсутствием какого-либо явного определения поведения. В этих трех различиях нет разницы; все они описывают «поведение, которое не определено».

Это немного расплывчато, когда сама модель памяти настолько расплывчата. Использование пространства, возвращаемого malloc() для хранения массива любого (одного) типа, по-видимому, нуждается в явном разрешении, которое я приводил выше. Поэтому можно утверждать, что использование этого пространства для непересекающихся массивов разных типов также требует явного учета и без него остается неопределенным поведением в главе 4.

Так что, если конкретный пример кода верен , что не так с аргументом, что он явно не определен и поэтому не определен, в части главы 4 из стандарта, который указан выше?

+0

'char *** A = malloc (90);' неправильный размер указателя 8 – 4386427

+0

@ 4386427, спасибо, я немного поспешил на мою арифметику. – Kyle

+0

@ 4386427, я получаю 6 указателей размера 8 имеют размер 6 * 8 = 48 и 4 строки длины 10 составляют 44, 48 + 44 = 92. – Kyle

ответ

1

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

Одна неприятная ошибка заключается в том, что, хотя существуют некоторые алгоритмы (например, для хеш-таблиц), которые будут очень хорошо работать с таблицей, которая изначально заполнена произвольными значениями (при условии, что это может быть или не быть правильно, код может быть способный определить, является ли значение правильным намного быстрее - O (1) vs O (N) - чем он мог найти правильное значение без начального предположения), записывая память как один тип и считывая как другой несимвольный тип дает неопределенное поведение, даже если тип назначения не будет иметь ловушечных представлений и даже если код будет работать правильно для любого значения, которое, возможно, прочитало бы чтение, в тех случаях, когда данные еще не были записаны в качестве нового типа.

Дано:

float *fp; 
uint32_t *ip; 

*fp = 1.0f; 
*ip = 23; 

поведение будет определяться, если FP и ф идентифицировать то же хранилище, а хранения потребовалось бы провести 23 позже. С другой стороны, учитывая:

float *fp; 
uint32_t *ip; 

*fp = 1.0f; 
uint32_t temp = *ip; 
*ip = 23; 

компилятор может воспользоваться Неопределенное поведение, чтобы изменить порядок операций таким образом, запись в * FP произошло после записи в * ф. Если данные повторно используются для чего-то вроде хеш-таблицы, вряд ли компилятор сможет с пользой применить такую ​​оптимизацию, но «умный» компилятор может переупорядочить записи бесполезных данных после записи полезных данных. Такие «оптимизации» вряд ли могут сломать вещи, но нет стандартного способа предотвратить их, кроме как путем освобождения и перераспределения хранилища (если вы используете размещенную систему и можете терпеть поражение производительности и возможную фрагментацию) или очистка хранилища перед повторным использованием (что может привести к тому, что операции O (1) в O (N)).

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