4

簡単なテストコードがあるとしましょう

typedef struct
{
    int first;
    int second;
    int third;
} type_t;

#define ADDRESS 0x12345678

#define REGISTER ((type_t*)ADDRESS)

const int data = (int)(&REGISTER->second)*2;

int main(void)
{
    volatile int data_copy;

    data_copy = data;

    while(1) {};
}

これは、ベアメタルARM用にCodeSourcery G ++(gcc 4.3.2)でコンパイルされています。また、非常に標準的なリンカースクリプトもあります。

Cで(main.cとして)コンパイルすると、オブジェクト「データ」は期待どおりにFlashに入ります。C ++で(main.cppとして)コンパイルされると、このオブジェクトはRAMに入れられ、フラッシュからRAMに値をコピーするだけの追加コードが追加されます(値はすでに計算されています。コピーするだけです!)。したがって、コンパイラはアドレスを計算できますが、どういうわけか「それを使用する」ことを望んでいません。問題の根本は、アドレスの乗算です。「* 2」の乗算がないと、両方のバージョンが期待どおりに機能します。「データ」はFlashに配置されます。また、「データ」が次のように宣言されている場合:

const int data = (int)(REGISTER)*2;

また、すべてが大丈夫です。

CおよびC++コンパイルのすべてのファイルは同一であり、唯一の違いはコンパイラーの呼び出しです。main.cppの場合はg ++、main.cの場合はgccです(警告のレベルに違いがあり、c ++ではRTTIと例外が無効になっています)。

この「C++の問題」を克服するための簡単でエレガントな方法はありますか?Cortex-M3のビットバンド領域のビットのアドレスのconst配列を作成するには、このような操作が必要です。これはバグですか、それともC ++コンパイラの奇妙な制限ですか?

「C」ファイルでデータオブジェクトを作成し、「extern」だけでデータオブジェクトを作成できることは知っています。C++にそれらを含めますが、それはあまりエレガントではありません[;

助けてくれてありがとう!

4

7 に答える 7

3

いくつかの問題があります。なぜあなたは住所を取り、それを整数に変換し、そして2を掛けるのですか?アドレスを乗算したり、アドレスをintとして格納したりしないでください。ポインターを使用して行う必要がある唯一の演算は、ポインターを減算する(オフセットを取得する)か、オフセットを使用してポインターを追加して別のポインターを取得することです。

C ++では、グローバル値の初期化のルールはCよりも少し緩いです。Cでは、値をコンパイル時定数に初期化する必要があります。その結果、コンパイラはconstグローバル値を読み取り専用メモリに配置できます。C ++では、値は必ずしもコンパイル時の定数ではない式に初期化でき、コンパイラーは実行時に初期値を計算するためのコードを生成することができます。この初期化コードはmain()、グローバルオブジェクトのコンストラクターと同様に、に入る前に呼び出されます。

定数アドレスを使用していて、プラットフォーム上でのanの大きさを知っているintので(私は32ビットを想定しています)、次のようにする必要があります。

次に、volatileキーワードの使用は完全に間違っています。 volatile変数をレジスタに保存しないようにコンパイラに指示します。変数は、読み取られるたびにメモリからリロードされ、書き込まれるたびに完全にメモリに書き込まれる必要があります。別のスレッドまたはシグナルハンドラーが予期せずにローカル変数の変更を開始することを期待しない限り、ローカル変数data_copyを実際には役に立たないものとして宣言します(非常に疑わしい)。volatileさらに、data_copyこれはアドレスの単なるコピーであり、読み取ろうとしているレジスタの内容ではありません。

あなたがしなければならREGISTERないことは、揮発性へのポインタとして宣言することです-これはvolatile、メモリマップドレジスタにアクセスするためのの明確な目的の1つです。コードは次のようになります。

#define REGISTER (*(volatile type_t *)ADDRESS)
#define DATA (*(const volatile int *)((ADDRESS+4)*2))

これにより、次のようなことを行うときに次のようになります。

REGISTER.first = 1;
REGISTER.first = 2;
int x = REGISTER.second;
int y = DATA;

それは常に適切なことをします:1から0x12345678への書き込み、2から0x12345678への書き込み、0x1234567cからの読み取り、および0x2468acf8からの読み取り。このvolatileキーワードは、これらの読み取りと書き込みが常に行われ、最適化されないようにします。コンパイラーは、への最初の書き込みを削除しませんREGISTER.first。これは、通常の変数の場合は冗長になります。

編集

あなたのコメントへの応答として、あなたのコメントに対するAndrewMedicoの応答を参照してください。2つのポインタのに2を掛けています。これは問題ありません。データ型に注意してください。また、カーネルについては何も言及していません。

sectiongccを取得して、次の属性を持つ特定のデータセクションに変数を配置できます。

const volatile int *data __attribute__((section("FLASH")) = /* whatever */;

適切なセクション名を使用してください。それが何であるかわからない場合は、Cコンパイラによって生成されたオブジェクトファイル(適切なセクションに配置すると言った)を取得して実行nmし、Cコンパイラがどのセクションに配置したかを確認します。

于 2009-08-16T16:20:20.587 に答える
3

正しい解決策は、stddef.hヘッダーのoffsetof()マクロを使用することです。

基本的にこれ:

const int data = (int)(&REGISTER->second)*2;

に置き換える必要があります

#include <stddef.h>
const int data = (int)(REGISTER + offsetof(type_t,second))*2;

そして、オブジェクトはCとC++の両方でFlashに配置されます。

于 2009-08-17T06:51:41.037 に答える
2

gcc変数属性を確認しましたか。おそらく「セクション」が変数の配置に役立ちます。

于 2009-08-16T16:38:01.887 に答える
1
  1. ポインタを掛けてはいけません。
  2. ポインタを「int」として格納するべきではありません。

あなたが望むことをするための合法的なC++の方法があると確信していますが、あなたが投稿したコードからそれが何であるかを理解することはできません。

于 2009-08-16T16:04:42.980 に答える
0

私が正しく理解していれば、あなたの全体的な目的はこの段落に要約されています:

Cortex-M3のビットバンド領域のビットのアドレスのconst配列を作成するには、このような操作が必要です。これはバグですか、それともC ++コンパイラの奇妙な制限ですか?

私はSTM32用のKeilコンパイラを使用していますが、それに付属する例の1つには、ビットバンドビットを設定およびクリアするためのマクロが含まれています。

#define RAM_BASE       0x20000000
#define RAM_BB_BASE    0x22000000

#define  Var_ResetBit_BB(VarAddr, BitNumber)    \
 (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 0)

#define Var_SetBit_BB(VarAddr, BitNumber)       \
 (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 1)

#define Var_GetBit_BB(VarAddr, BitNumber)       \
 (*(vu32 *) (RAM_BB_BASE | ((VarAddr - RAM_BASE) << 5) | ((BitNumber) << 2)))

これらがまさにあなたが探しているものではない場合、私はそれらがあなたのニーズに合うように修正されることができると思います。

于 2009-08-17T08:18:43.717 に答える
0

編集

ポインタを乗算したいと言いますが、それはおそらく間違っています。覚えておいてください:おそらくあなたが望むものとは(int)(REGISTER) * 2等しくない(int)(0x12345678 * 2)ものに等しくなります。あなたはおそらく欲しいでしょう:これは構造体の最後のバイトを過ぎた2つの整数です。0x2468ACF0REGISTER + sizeof(int) * 2

元の回答:

これは、「 struct hack 」を実行しようとして失敗したように見えます(この例ではc99スタイルを使用していますが、C89でも問題なく機能し、最後の要素として1の配列が必要です)。おそらくあなたが欲しいのはこのようなものです:

typedef struct {
    int first;
    int second;
    int third;
    unsigned char data[1]; /* we ignore the length really */
} type_t;

type_t *p = (type_t *)0x12345678;

p->data[9]; /* legal if you **know** that there is at least 10 bytes under where data is */

これの一般的な使用法は、次のようなmalloc割り当て構造です。

type_t *p = malloc((sizeof(int) * 3) + 20) /* allocate for the struct with 20 bytes for its data portion */
于 2009-08-16T16:11:50.797 に答える
0

周辺機器の読み取り/書き込みに最も安全にアクセスするには、volatile定義とoffset定義を使用する必要があります。周辺アドレスを構造体としてキャストしても、アライメントやオフセットは保証されません。

#define UART_BASE_REG ((volatile uint32_t*)0x12341234)
#define UART_STATUS_REG (UART_BASE_REG + 1)

..。

于 2009-08-16T16:54:05.047 に答える