Пытаюсь разобраться как работают структуры, и наткнулся на один непонятный момент. Возьмем к примеру, вендор производителя (stm32f4xx.h), в нем есть, кусок кода, содержащий набор регистров для работы с портами ввода-вывода.
Здесь создается структура и она же объявляется как новый тип
Код:
typedef struct { __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */ __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */ __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */ __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */ __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */ __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */ __IO uint16_t BSRRL; /*!< GPIO port bit set/reset low register, Address offset: 0x18 */ __IO uint16_t BSRRH; /*!< GPIO port bit set/reset high register, Address offset: 0x1A */ __IO uint32_t BSRR; __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */ __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */ } GPIO_TypeDef;
Далее следует код:
Код:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
Этой строчкой мы создаем указатель GPIOA на структуру типа GPIO_TypeDef.
И дальше, я не могу разобраться.
Указатель тоже своего рода переменная и должен занимать место в памяти и где-то находится, но я так и не смог найти его место положение. Вопрос, так где же он лежит ?
Вовсе не так. Процесс компиляции Си-кода проходит 2 этапа: 1. препроцессор 2. компиляция
В данном случае имеет место только первый вариант. Препроцессор сразу подставляет вместо GPIOA указанную строку, он не создаёт указателей или каких-либо иных объектов.
Этой строчкой мы создаем указатель GPIOA на структуру типа GPIO_TypeDef.
не создаем. После такого объявления директивы препроцессор заменит в остальной программе "GPIOA" на "((GPIO_TypeDef *) 0x094566)" - цифирь от балды, это GPIOA_BASE - что, в свою очередь, означает "обратиться к области памяти по адресу 0x94566 как к содержащей переменную типа GPIO_TypeDef"
Обязательным условием долгой и стабильной работы Li-FePO4-аккумуляторов, в том числе и производства EVE Energy, является применение специализированных BMS-микросхем. Литий-железофосфатные АКБ отличаются такими характеристиками, как высокая многократность циклов заряда-разряда, безопасность, возможность быстрой зарядки, устойчивость к буферному режиму работы и приемлемая стоимость. Но для этих АКБ очень важен контроль процесса заряда и разряда для избегания воздействия внешнего зарядного напряжения после достижения 100% заряда. Инженеры КОМПЭЛ подготовили список таких решений от разных производителей.
Вы когда решаете применить незнакомое вам слово, хоть погуглите что оно означает...
Верное замечание, вендор и есть производитель. Писал, когда время уже было позднее, поэтому может быть и ошибся. И признаюсь честно, узнал это слово из ваших топиков, где вы кому то объясняли структуру проекта или что-то схожее с этим, но найти это сообщение снова не смог. А вообще нужно было написать "заголовочный файл вендора", поправте меня, если я снова ошибаюсь.
Вовсе не так. Процесс компиляции Си-кода проходит 2 этапа: 1. препроцессор 2. компиляция
В данном случае имеет место только первый вариант. Препроцессор сразу подставляет вместо GPIOA указанную строку, он не создаёт указателей или каких-либо иных объектов.
Что-то, я не до конца понимаю, а что происходит дальше, после того, как он подставил.
Добавлено after 6 minutes 30 seconds:
arkhnchul писал(а):
" - цифирь от балды, это GPIOA_BASE - что, в свою очередь, означает "обратиться к области памяти по адресу 0x94566 как к содержащей переменную типа GPIO_TypeDef"
Это я уже выучил на изусть, но как происходит это обращение, я что-то до конца не понимаю.
Компания EVE выпустила новый аккумулятор серии PLM, сочетающий в себе высокую безопасность, длительный срок службы, широкий температурный диапазон и высокую токоотдачу даже при отрицательной температуре.
Эти аккумуляторы поддерживают заряд при температуре от -40/-20°С (сниженным значением тока), безопасны (не воспламеняются и не взрываются) при механическом повреждении (протыкание и сдавливание), устойчивы к вибрации. Они могут применяться как для автотранспорта (трекеры, маячки, сигнализация), так и для промышленных устройств мониторинга, IoT-устройств.
В данном случае stm32f4xx.h - заголовочный файл целой серии МК, и он может быть написан также создателем компилятора, поэтому связывать его с производителем не нужно.
_________________ Каждый имеет право на свое личное ошибочное мнение.
У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.
В данном случае stm32f4xx.h - заголовочный файл целой серии МК, и он может быть написан также создателем компилятора, поэтому связывать его с производителем не нужно.
Неа, хэдеры периферии пишет вендор. Это требование CMSIS, читаем спецификацию на CMSIS...
Этой строчкой мы создаем указатель GPIOA на структуру типа GPIO_TypeDef.
Нет. Этой строчкой мы приводим GPIOA_BASE (константа-дефайн, определенная где-то выше) к типу "указатель на GPIO_TypeDef".
Проще говоря, это один из способов наложить структуру GPIO_TypeDef на область памяти, начиная с ячейки номер GPIOA_BASE.
Я до сих пор, до конца не могу понять, как именно происходит приведение константы к типу. Как, с вашей точки зрения, происходит это на белее низком уровне.
Это скорее арифметика указателей, а не приведение константы к типу. На основе констант рассчитываются указатели. У нас есть адрес в памяти и структура описывающая смещения относительно этого адреса.
Address offset это и есть смещение каждого поля относительно адреса в памяти. Собственно чтоб предоставить удобную форму отображения смещения эта структура и нужна, чтобы вместо
Я до сих пор, до конца не могу понять, как именно происходит приведение константы к типу.
Для начала требуется осознать, что на уровне железа типов нет. Есть только огромный линейный массив байт, каждый из которых пронумерован. Интерпретировать их можно как угодно.
Для компилятора тип - это правило, по которому он будет интерпретировать один или несколько байт, расположенных начиная с заданного адреса.
"Приведение к типу" - переопределение правила, по которому компилятор будет обращаться к байтам по указанному адресу.
int32_t x; - "компилятор, выбери какой хочешь адрес в оперативной памяти, и начиная с него зарезервируй четыре байта, которые интерпретируй в дальнейшем как целое число со знаком; а называться эти четыре байта будут x".
Компилятор понимает это, и в дальнейшем будет обращаться с этими четырьмя байтами как с целым тридцатидвухибитным числом (big endian или little endian - зависит от соглашения), причем при вычислениях будет использовать дополнительный код, чтобы учитывать знак.
Начнем с простого.
temp1 = &x; - "компилятор, запиши в temp1 стартовый адрес той области, которая x". При этом компилятор знает, что к этой области надо обращаться по правилам, адекватным для целого числа со знаком. Теперь если мы напишем
(*temp) = 500;
компилятор запишет в x (потому что в temp лежит адрес x) число 500 (0x000001F4) с учетом установленных правил. То есть, в случае little endian, запишет 0xF4 в байт с самым младшим адресом, 0x01 в следующий и обнулит остальные два.
Теперь будем приводить типы.
y = *((uint8_t *)(&x)); - "компилятор, мы договаривались, что по адресу, содержимое которого мы называли x, лежит целое тридцатидвухбитное. Теперь сделай вид, что там целое беззнаковое восьмибитное, и прочти его по этому правилу."
Компилятор прочтет один байт с адреса, по которому лежит x. А там, с учетом присвоения выше, лежит 0xF4 (244 в десятичной системе счисления). Вот это число и будет записано в y.
z = ((uint8_t *)(&x))[1]; - "компилятор, интерпретируй стартовый адрес x как указатель на массив беззнаковых целых, и излеки из него первое значение."
В z окажется 1. Понимаете, почему?
В случае константного адреса все происходит так же, только мы не даем компилятору произвольно выбрать адрес, а жестко диктуем его значение.
((uint8_t *)0x100) - "компилятор, понимай 0x100 как номер ячейки, начиная с которой лежит беззнаковое целое восьмибитное число."
_________________ Разница между теорией и практикой на практике гораздо больше, чем в теории.
Ну вот, мои труды уже входят в наследие для потомков.
Мне тоже понравилось!) Скину в txt и распечатаю. До прочтения вашего поста у меня было мнение, что "ну не умеют программисты объяснять/преподавать, не умеют, сколько раз проверяли!!!!!" Но нет, бывают исключения
PS: последнее, где массив [1]- непонятно(((
Последний раз редактировалось Sanchosd Вт апр 02, 2019 07:07:50, всего редактировалось 1 раз.
Кстати там опечатка, только заметил... Присваиваю я там в "temp1", а потом рассуждаю про "temp". Но, видимо, все эти два года и так было понятно... Даже не знаю, править теперь, или нет.
Цитата:
последнее, где массив [1]- непонятно(((
Скобочки - синтаксический сахар (красивая запись) для инкремента указателя с его последующим разыменованием.
То есть, записи
a[x] = N;
и
*(a+x) = N;
эквивалентны.
То есть,
z = ((uint8_t *)(&x))[1]
есть то же самое, что
z = *(((uint8_t *)(&x)) + 1);
а это извлечение второго байта начиная с адреса &x.
Цитата:
До прочтения вашего поста у меня было мнение, что "ну не умеют программисты объяснять/преподавать, не умеют, сколько раз проверяли!!!!!"
Это, наверное, оттого, что я не программист, а электронщик.
Всяко, я рад, что мой пост имеет такой успех.
_________________ Разница между теорией и практикой на практике гораздо больше, чем в теории.
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 14
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения