Перейти к содержанию
    

Плавный переход C -> C++ под МК

P.S. Резюмирую. Да, я немного плыву в теме накладных расходов при использовании классов и виртуальных методов. И ещё много в чём. Но не я один плыву. Например, тут утверждалось постоянно, что будет использоваться VTABLE при использовании виртуальных методов. Чтож, будет. Но только при определённых обстоятельствах, котоыре не уточнялись. А не всегда. Так что... вывод каждый может делать сам.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 часа назад, haker_fox сказал:

Например, тут утверждалось постоянно, что будет использоваться VTABLE при использовании виртуальных методов. Чтож, будет. Но только при определённых обстоятельствах, котоыре не уточнялись. А не всегда. Так что... вывод каждый может делать сам.

Если Вы о том, что оно используется только при вызове через указатель на базовый класс, так вроде это должно быть само собой очевидным. Как и само собой должно быть очевидным, что если вызовы методов через указатели на базовый класс не используются, то и нет никакого смысла в виртуальных членах. О чём Вам компилятор как бы косвенно и намекает, удаляя обращения к VTABLE (да и саму VTABLE наверняка удаляет, если нигде больше в коде нет таких вызовов).

Так что - если нет нигде вызовов например write() через указатель на базовый класс, то зачем её сделали virtual?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

8 hours ago, jcxz said:

если нет нигде вызовов например write() через указатель на базовый класс

Сейчас нет. Завтра появятся. Что же мне добавлять virtual  в спешном порядке?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот что интересно.

Допустим, разрабатывается класс FIFO для UART, который будет работать в DMA-режиме. Для разных UART-модулей, во-первых, разные базовые адреса периферии, во-вторых, существуют зависимости, которые напрямую к UART не относятся (однако класс должен получать информацию на основе этих зависимостей). Как пример такой зависимости - регистры статуса DMA: там флаги всех потоков кучкой распиханы в пару регистров. У меня на ум приходит только добавлять в класс приватные поля с нужными масками, адресами регистров и т.д. и инициализировать их при объявлении конкретного объекта (ведь у меня может быть, например, 5 разных каналов UART). Это нормальный подход или кривой? Если это нормальный подход, то вопрос номер два: указатель на базовый адрес периферии (например, DMA-потока) объявлять в классе обобщенным указателем void * или типом структуры регистров? Как по мне void * имеет то преимущество, что не нужно в hpp-файле подключать stm32f4xx.h со всем его жирным содержимым ради лишь определения DMA_Stream_TypeDef. А в нужном месте просто приводить void * к этому DMA_Stream_TypeDef. Где-то видел такой прием, вопрос в адекватности такого подхода.

P.S. Да, я понимаю, что если структура управляющих битов для всех периферийных узлов регулярна (т.е. на основе лишь номера периферийного узла или того же DMA-потока можно рассчитать все положения уже конкретных управляющих регистров/битов/масок и т.д.), то можно обойтись передачей в конструктор класса лишь одного числа. Но это а) не гибко, т.к. бывают не регулярные структуры и б) выполняется в run-time, что плохо.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Arlleex сказал:

Как пример такой зависимости - регистры статуса DMA: там флаги всех потоков кучкой распиханы в пару регистров. У меня на ум приходит только добавлять в класс приватные поля с нужными масками, адресами регистров и т.д.

Я храню номер канала ПДП, маски и адреса регистров расчитываются на лету - в кортексах это почти бесплатно.

1 час назад, Arlleex сказал:

вопрос номер два: указатель на базовый адрес периферии (например, DMA-потока) объявлять в классе обобщенным указателем void * или типом структуры регистров? 

Перефразируя закон Мерфи "Если в функцию можно передать указатель на что угодно, в нее обязательно передадут указатель на какую-нибудь гадость.

1 час назад, Arlleex сказал:

Как по мне void * имеет то преимущество, что не нужно в hpp-файле подключать stm32f4xx.h со всем его жирным содержимым ради лишь определения DMA_Stream_TypeDef.

uart_dma(struct DMA_Stream_TypeDef * dma), т.е. forward declaration не спасет? Вы же саму структуру в класс не засовываете, только указатель на нее.

Добавлено: Я многие функции делаю встраиваемыми, поэтому подключать stm32f4xx приходится и я даже не беру в голову его размер - он же не в выходной файл добавляется. Более того, я подключаю не stm32f4xx, а свой stm32.h, в который подключается нужный stm32xxxxx,h в зависимости от выбранного в проекте целевого контроллера, ибо у f4 и f2 ПДП одинаковый.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Arlleex сказал:

Как по мне void * имеет то преимущество, что не нужно в hpp-файле подключать stm32f4xx.h со всем его жирным содержимым ради лишь определения DMA_Stream_TypeDef.

Просто в .h перед объявлением функции напишите (неполное объявление структуры): struct DMA_Stream_TypeDef;

Этого достаточно. Подключать ничего не нужно. Везде так делаю:

struct SocketTarget;
int HttpBuild(char const *url, char *buf, uint sizbuf, char const *httpTail, SocketTarget * = NULL);

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Возможны ли еще какие-либо способы инициализации констант-членов класса, кроме как через список инициализации?

В классе, например, объявлен константный указатель на некий u32

class MyClass
{
  volatile u32 *const ptr;
  
  public:
    MyClass(u32 N);
};

Теперь на основе N я хочу создать некий указатель, который должен лечь в ptr. Т.е. формирование ptr будет осуществляться в каком-то (может даже не столь тривиальном) коде исходя из N, что был передан в конструктор. Это можно было бы сделать в теле самого конструктора, не будь const у ptr. Копать в сторону constexpr()-функций?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

12 минут назад, Arlleex сказал:

Возможны ли еще какие-либо способы инициализации констант-членов класса, кроме как через список инициализации?

А почему не объявить/определить просто отдельную структуру для const данных? И указатель на неё положить в класс.

У меня например так (только я делаю наоборот: главный - const-класс, а в нём содержится указатель на структуру с переменными):

Скрытый текст

struct UartHndVar {
  enum { //спец.состояния eposTx
    TX_START = 65533, TX_DMA, TX_STOP};
  u16 volatile rposTx, wposTx, eposTx;
  u16 volatile rposRx, wposRx;
  u16 volatile rxNDTR;
  u16 txd;
  u8 lcnt;
  #if UART_CHK_RDMA_OVER
  u8 nmar;
  #endif
  OsFlag txNoFull, rxNoEmpty;
};

struct UartHnd {
  struct IoIsrIfcr {
    u32 volatile ISR;
    u32 unuse;
    u32 volatile IFCR;
  };
  UartHndVar *var;
  HwRegsUART *uart;
  char *bufTx, *bufRx;
  u16 sizTx;
  u16 sizTxFlush;
  u16 sizRx;                   //должно быть >= UART_RDMA_CHUNK*3 и кратно UART_RDMA_CHUNK
  u8 nDMASTRtx, nDMASTRrx;
  u8 uartNvic, txDmaNvic, rxDmaNvic;
  u8 rccUart;
  u8 trapRegion;
  u8 overlock;
  u8 dmaTxStat, dmaRxStat;
  IoIsrIfcr *dmaTxISR, *dmaRxISR;
  HwRegsDMA::T_STREAM *streamTx, *streamRx;
  u32 streamTxCR, streamRxCR;
  void Init() const;
  void Isr() const;
  void IsrTDMA() const;
  void IsrRDMA() const;
  void StartTDMA() const;
  void StartTx() const;
  void StartTx(int) const;
  u32  StartRx(u32) const;
  void Stop() const;
  void Timer() const;
  void TimerFast() const;
  void Tx(int) const;
  void Tx(void const *, uint) const;
  int  Rx() const;
  int  Rx(u32, u32) const;
  int  Rx(void *, uint) const;
  int  NoEmptyTx() const { int i = var->rposTx; return i - var->wposTx; }
  int  NoEmptyRx() const { int i = var->rposRx; i -= var->wposRx; return i | var->rxNDTR >> 15; }
  private:
  void Clear() const;
};

 

Внутри членов UartHnd всё удобно:

__no_init struct {
  char tx[400];
  char rx[UART_RDMA_CHUNK_ALIGN(150)];
} static bufTerm @ ".dma";

static __no_init UartHndVar termHndVar;

static UartHnd const termUartHnd = {
  .var = &termHndVar,
  .bufTx = &bufTerm.tx[0],
  .bufRx = &bufTerm.rx[0],
  .sizTx = sizeof(bufTerm.tx),
  .sizTxFlush = sizeof(bufTerm.tx) / 3,
  .sizRx = sizeof(bufTerm.rx),
  UartHnd_SET(term, TRAPR_TERMINAL, ms2tkt(200))
};

...

void UartHnd::Timer() const
{
  CPU_SR_ALLOCATE();
  ENTR_CRT_SECTION();
  uint i = var->rposTx;
  if (i != var->wposTx) if (var->eposTx < (uint)var->TX_START) {
    var->eposTx = var->TX_START;
    EXIT_CRT_SECTION();
    StartTx();
    return;
  }
  EXIT_CRT_SECTION();
}

PS: Если я правильно понял о чём Вы...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Идея в том, что я хочу объявить в классе указатели на нужные мне регистры (или вовсе на блок регистров нужной периферии), основываясь лишь на номере этой периферии (или номере DMA-потока). Маски/биты (при необходимости) тоже. И получится, что каждый экземпляр класса будет содержать нужные конкретно ему регистры периферии и т.д. Ну а const, как и в любых подобных случаях, для невозможности явного присвоения в коде.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 minutes ago, Arlleex said:

Идея в том, что я хочу объявить в классе указатели на нужные мне регистры (или вовсе на блок регистров нужной периферии), основываясь лишь на номере этой периферии (или номере DMA-потока). Маски/биты (при необходимости) тоже. И получится, что каждый экземпляр класса будет содержать нужные конкретно ему регистры периферии и т.д. Ну а const, как и в любых подобных случаях, для невозможности явного присвоения в коде.

Я это делаю через шаблоны, один из параметров - базовый адрес соотв. периферии.

Инстанцирование на этапе объявления.

Вся общая для всех портов одного вида реализация - в классе родителе этих шаблонов. Скрыта в cpp, поэтому не "захламляет" hpp файл.

В ходе мучений и проб пришел только к этой схеме. Она меня устраивает.

Извиняюсь за сумбур в описании ))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

18 minutes ago, Arlleex said:

Копать в сторону constexpr()-функций?

Да, можно так: https://godbolt.org/z/E1djrKEdb

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, Сергей Борщ сказал:

...т.е. forward declaration не спасет?...

Вот попробовал сейчас, кстати... Не спасает(( Тут, видимо, дело в том, что в h-файле этот DMA_Stream_TypeDef определен как typedef struct {...}DMA_Stream_TypeDef; (то есть в стиле пуре-Си). Соответственно, либо менять на C++-стиль (struct DMA_Stream_TypeDef {};), либо приписывать тэг структуре (а этот хидер менять не хочется, вообще говоря). Значит придется в hpp подключать общий stm32f4xx.h. Страшного тут, конечно, ничего нет, но и полезного мало: теперь везде, где подключается fifo.hpp будет виден "мусор" из stm32f4xx.h и мозолить глаза. Не знаю, отчего у меня такая мания стараться отделять мух от котлет:smile: Просто хочется, чтобы пробираясь к top-level коду (там, где бизнес-логика), ничего лишнего "видно" не было - никаких битовых масок периферии процессора и т.д.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Использую C++11. Задал несколько enum class внутри класса cUARTDMARx, чтобы пользователь класса выбирал из них

namespace nsFIFO {
  class cUARTDMARx {
    ...
    
    public:
      enum class eDMAMod {
        DMA1n = 1, DMA2n
      };
      enum class eDMAStr {
        STR0n = 0, STR1n, STR2n, STR3n,
        STR4n,     STR5n, STR6n, STR7n
      };
      
      cUARTDMARx(eDMAMod mod, eDMAStr str);
      
      ...
  };
}


Можно ли теперь монструозный синтаксис

using namespace nsFIFO;

cUARTDMARx fifo(cUARTDMARx::eDMAMod::DMA1n, cUARTDMARx::eDMAStr::STR0n);

заменить как-то на нечто покороче

...

cUARTDMARx fifo(DMA1n, STR0n);

, т.е. каким-то образом раскрыть пространство имен для этих enum class так же, как это было с namespace nsFIFO? Беглый поиск в интернете говорит, что так нельзя сделать в C++11, а можно только начиная с C++20. Или врут? Перетаскивать эти enum class в nsFIFO (вытащив из cUARTDMARx) очень не хочу - эти перечисления живут только для конкретного класса (в данном примере, конечно, они несколько универсальные, но в общем случае там могут быть нужные только этому классу константы).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, Arlleex said:

Можно ли теперь монструозный синтаксис

Сокращать можно только внутри этого самого метода, снаружи - нет, нужно конкретизировать области видимости в каждом объявлении.

Напрмер, делаю так:

RCC.hpp:

namespace stm32g0
{
  ....
  
	class Clock
	{
	public:		
      
		struct PLL
		{
			using Source = enum { HSI, HSE };
			using Multiplier = uint8_t;
			using Prescaler = uint8_t;
			using Divider = uint8_t; // Postscaler
			
			static void setClockSource(Source);
			static void setPrescaler(Prescaler);
.....
    
		struct HSI
		{
...      

 

Application.cpp:

	stm32g0::Clock::initialize();
	stm32g0::Clock::PLL::setClockSource(stm32g0::Clock::PLL::HSI); // 16 MHz
	stm32g0::Clock::PLL::setPrescaler(1);	// 16 Mhz = 16 MHz
	stm32g0::Clock::PLL::setMultiplier(8); // 16 MHz x 8 = 128 MHz
....

 

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

 

Кстати,  а почему в C++11 используете такую странную форму объявления?

enum class eDMAMod {

 

от себя рекомендую переходить на using. Благодаря ей, отказался от typedef как от страшного кошмара ))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...