私は現在、Atmel AVR マイクロコントローラー (gcc) を使用していますが、一般的なマイクロコントローラーの世界、つまり通常はシングル スレッドであるが割り込みを伴うものに回答を適用したいと考えています。
volatile
ISR で変更できる変数にアクセスするときに C コードで使用する方法を知っています。例えば:
uint8_t g_pushIndex = 0;
volatile uint8_t g_popIndex = 0;
uint8_t g_values[QUEUE_SIZE];
void waitForEmptyQueue()
{
bool isQueueEmpty = false;
while (!isQueueEmpty)
{
// Disable interrupts to ensure atomic access.
cli();
isQueueEmpty = (g_pushIndex == g_popIndex);
sei();
}
}
ISR(USART_UDRE_vect) // some interrupt routine
{
// Interrupts are disabled here.
if (g_pushIndex == g_popIndex)
{
usart::stopTransfer();
}
else
{
uint8_t value = g_values[g_popIndex++];
g_popIndex &= MASK;
usart::transmit(value);
}
}
g_popIndex は ISR の内部で変更され、ISR の外部でvolatile
アクセスされるため、その変数へのメモリ アクセスを最適化しないようにコンパイラに指示するように宣言する必要があります。ISR によって変更されていないため、私が間違っていない限り、宣言する必要がないことに注意してg_pushIndex
くださいg_values
。volatile
クラス内のキューに関連するコードをカプセル化して、再利用できるようにしたいと考えています。
class Queue
{
public:
Queue()
: m_pushIndex(0)
, m_popIndex(0)
{
}
inline bool isEmpty() const
{
return (m_pushIndex == m_popIndex);
}
inline uint8_t pop()
{
uint8_t value = m_values[m_popIndex++];
m_popIndex &= MASK;
return value;
}
// other useful functions here...
private:
uint8_t m_pushIndex;
uint8_t m_popIndex;
uint8_t m_values[QUEUE_SIZE];
};
Queue g_queue;
void waitForEmptyQueue()
{
bool isQueueEmpty = false;
while (!isQueueEmpty)
{
// Disable interrupts to ensure atomic access.
cli();
isQueueEmpty = g_queue.isEmpty();
sei();
}
}
ISR(USART_UDRE_vect) // some interrupt routine
{
// Interrupts are disabled here.
if (g_queue.isEmpty())
{
usart::stopTransfer();
}
else
{
usart::transmit(g_queue.pop());
}
}
上記のコードは間違いなくより読みやすいです。volatile
とありますが、この場合どうすればよいのでしょうか。
1) それはまだ必要ですか? メソッドを呼び出すと、関数が宣言されている場合でも、Queue::isEmpty()
への最適化されていないアクセスが保証されますか? 私はそれを疑います。コンパイラがヒューリスティックを使用してアクセスを最適化する必要がないかどうかを判断することは知っていますが、一般的な解決策としてそのようなヒューリスティックに頼るのは嫌いです。g_queue.m_popIndex
inline
Queue::m_popIndex
volatile
2)機能する(そして効率的な)解決策は、クラス定義内でメンバーを宣言することだと思います。ただし、クラスの設計者は、Queue
どのメンバー変数がvolatile
. 将来のコード変更にうまく対応できません。また、一部のインスタンスがISR 内で使用されていない場合でも、すべてのQueue
インスタンスがメンバーを持つようになりました。volatile
3) クラスをビルトインのように見る場合、ISR で変更され、ISR の外部でアクセスされるため、グローバル インスタンス自体を としてQueue
宣言するのが自然な解決策だと思います。ただし、オブジェクトに対して呼び出すことができるのは関数のみであるため、これはうまく機能しません。突然、すべてのメンバー関数を宣言する必要があります( ISR 内で使用されるものだけでなく)。繰り返しますが、デザイナーはどのようにしてそれを事前に知ることができますか? また、これはすべてのユーザーにペナルティを課します。すべてのメンバー関数を複製し、クラスにと non-の両方のオーバーロードを含める可能性がまだあるため、非ユーザーはペナルティを受けません。かわいくない。g_queue
volatile
volatile
volatile
Queue
volatile
const
Queue
Queue
volatile
volatile
volatile
4)クラスは、必要に応じてすべてのメンバー変数にQueue
オプションで追加できるポリシー クラスでテンプレート化できます。volatile
繰り返しますが、クラス設計者は事前にそのことを知っておく必要があり、解決策は理解するのがより複雑になりますが、まあまあです。
これに対する簡単な解決策が欠けているかどうか知りたいです。ちなみに、私は (まだ) C++11/14 をサポートせずにコンパイルしています。