Выравнивание указателя наследования C struct
Фон
Я создал основную структуру данных связанных списков, в основном для обучения. Одна из целей этого списка заключалась в том, что он может обрабатывать разные структуры данных. Поэтому я попробовал свои силы в составе структуры, чтобы имитировать "наследование" в C. Вот структуры, которые составляют основу для моего связанного списка.
typedef struct Link {
struct Link* next;
struct Link* prev;
} Link;
typedef Link List;
В моей реализации я решил иметь часовое node, которое служит как головой, так и хвостом списка (вот почему Link == List).
Чтобы сделать список действительно обрабатываемым данными, структура просто включает в себя структуру Link в качестве первого элемента:
typedef struct {
Link link;
float data;
} Node;
Таким образом, связанный список выглядит следующим образом.
┌───┬───┬───┐ ┌───┬───┐ ┌───┬───┬───┐
... <--->│ P │ N │ D │<--->│ P │ N │<--->│ P │ N │ D │<---> ...
└───┴───┴───┘ └───┴───┘ └───┴───┴───┘
End Node myList First Node
List myList;
Node node1 = {{}, 1.024};
....
Node nodeN = {{}, 3.14};
list_init(&myList) // myList.next = &myList; myList.prev = &myList;
list_append(&myList, &node1);
....
list_append(&myList, &nodeN);
Вопрос
Чтобы пройти этот список, указатель Node
сначала указывает на Первый Node. Затем он перемещается по списку, пока он снова не укажет на часового, а затем остановится.
void traverse()
{
Node* ptr;
for(ptr = myList.next; ptr != &myList; ptr = ptr->link.next)
{
printf("%f ", ptr->data);
}
}
Мой вопрос заключается в строке ptr != &myList
. Есть ли проблема выравнивания указателя с этой строкой?
Цикл for корректно создает предупреждения: (warning: assignment from incompatible pointer type
и warning: comparison of distinct pointer types lacks a cast
), которые можно отключить, выполнив то, что он говорит, и произнесение на Node*
. Однако, это DumbThingToDo ™? Я никогда не получаю доступ к ptr->data
, когда он указывает на &myList
, когда цикл завершается один раз ptr == &myList
.
TL;DR
В C-структурах a Base*
может указывать на Derived
, если Base
является первым членом в Derived
. Может ли Derived*
указывать на Base
, если ни один из Derived
не доступен для определенных членов?
EDIT: замените соответствующие вызовы функций их эквивалентным встроенным кодом.
Ответы
Ответ 1
Престижность вашей презентации.
Я думаю, что ваша реализация должна работать нормально, потому что C гарантирует, что адрес структуры является адресом ее начального члена. Отложите в сторону утверждения C о выравнивании структурных элементов, эта гарантия должна означать, что, пока ваша реализация всегда ставит Link в качестве первого члена, это не должно вызывать проблемы с выравниванием.
из здесь: C99 §6.7.2.1:
13 Внутри объекта структуры члены, не являющиеся битовыми полями, и блоки в которых находятся битовые поля, имеют адреса, которые увеличиваются в порядке в котором они объявлены. Указатель на объект структуры, подходящим образом преобразуется, указывает на его начальный член (или если этот член является бит-поле, затем в блок, в котором он находится) и наоборот. В объекте структуры может быть неназванное заполнение, но не на его начало
Это должно быть то, что вы хотели сказать о Base *
и Derived *
, хотя в чистом C. такая вещь не существует. Это просто структуры, которые имеют одинаковый макет памяти.
Однако я думаю, что это немного хрупко реализовать, как это, потому что Node и Link напрямую зависят друг от друга. Если вы изменили структуру Node, ваш код станет недействительным. На данный момент я не вижу смысла иметь дополнительный struct Link
, кроме того, что вы можете просто написать новый Node для нового типа повторного использования ссылки.
На самом деле существует связанная реализация списка, которая сразу же пришла мне в голову, когда я увидел ваш пост и работал таким образом, который очень похож на то, как вы собираетесь использовать свой список: список ядра
Он использует тот же List-element (list_head):
struct list_head {
struct list_head *next, *prev;
};
Он содержит макрос этой функции:
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
Если вы посмотрите на способ реализации макроса, вы увидите, что он предлагает итерацию над записями списка, не зная ничего о макете записей, в которых содержится список. Предполагая, что я интерпретирую ваше намерение правильно, я думайте, что это так, как вы хотели бы, чтобы это было.
Ответ 2
В C-структурах Base * может указывать на Derived, если Base является первым членом в Derived. Может ли Derived * указывать на Base, если ни один из Derived конкретных членов не доступен?
Если вы имеете в виду "Может ли Derived" указывать на произвольную базу (включая ту, которая не является первым членом производного объекта) ", тогда: Технически, нет. Мое понимание Отчет о дефектах № 74 - это требования к выравниванию могут быть разными.
Q: Если структура имеет поле типа t, может ли выравнивание требования поля отличаются от требований к выравниванию объектов одного типа, не являющихся членами структур? Если ответ на (a) есть "да", тогда, когда это применимо, остальные следует предположить, что вопросы были заданы для обоих объектов в пределах структуры и объекты вне структуры.
A: В подпункте 6.1.2.5 говорится: "... указатели на квалифицированные или неквалифицированные версии совместимых типов должны иметь одинаковое представление и требования к выравниванию. ' В подпункте 6.5.2.1 говорится:" Каждый член небитового поля структуры или объекта объединения выровнен в определенного в соответствии с его типом ". И позже," Следовательно, в структурном объекте может быть неназванное дополнение,... по мере необходимости, чтобы добиться соответствующего выравнивания ". а) это возможно для реализации для представления обобщенных требований к удовлетворяют условию 6.1.2.5. Эти требования могут быть усиление с использованием определенного для реализации поведения в подпункте 6.5.2.1. Да, требования к выравниванию могут быть отличается.
Ответ 3
Я написал это в комментариях к ответу ouah, но я представлю его как ответ сам по себе.
Это верно, поскольку ouah пишет, что код в вашем вопросе может иметь теоретическую проблему с выравниванием, если вы идете по стандарту C. Тем не менее, я не думаю, что существует одна архитектура, в которой ваш код может (или маловероятен) работать, на ABI которого есть такие шаблоны выравнивания. По крайней мере, я знаю, что я знаю, и это включает в себя опыт работы с несколькими различными настольными и встроенными архитектурами. Я также не могу представить себе архитектуру, которая могла бы получить что-либо от таких свойств выравнивания.
Также стоит отметить, что эта схема действительно используется на практике довольно часто; это похоже на то, что вы всегда можете поместить int
в указатели, поскольку это не гарантируется никаким межплатформенным стандартом, но практически нет платформы, на которой это не так, и люди делают это все время.