はじめに
どうも、レガシー組込みエンジニアの@yagisawaです。
最近(と言いたいところですが2年前から)細々と組込みRustにチャレンジしているのですが、Rustのポインタの扱いがなかなかCのようにいかなかったので調べたことをまとめました。
解説に使うコード例はThe Embedonomicon – A main interfaceにある以下を扱います。
extern "C" {
static mut _sbss: u8;
static mut _ebss: u8;
static mut _sdata: u8;
static mut _edata: u8;
static _sidata: u8;
}
let count = &_ebss as *const u8 as usize - &_sbss as *const u8 as usize;
ptr::write_bytes(&mut _sbss as *mut u8, 0, count);
let count = &_edata as *const u8 as usize - &_sdata as *const u8 as usize;
ptr::copy_nonoverlapping(&_sidata as *const u8, &mut _sdata as *mut u8, count);
上記コードが何を意味しているかについては拙作の以下の記事をご参考ください。
ポインタ(アドレス)を数値にキャストする
組込みソフトではアドレスを数値として扱って加減算したいことがあります。コード例のようにbssセクションの終了アドレスから開始アドレスを引いてbssセクションのサイズを求めたいとき等です。
Cでは
unsigned int count = (unsigned int)&_ebss - (unsigned int)&_sbss;
と直接ポインタをunsigned intにキャストできますが、Rustでは
let count = &_ebss as *const u8 as usize - &_sbss as *const u8 as usize;
のようにしなければならないようです。
わかりやすいように括弧を付けると
let count = ((&_ebss as *const u8) as usize) - ((&_sbss as *const u8) as usize);
となり、&_ebssを一旦*const u8にキャストしてから更にusizeにキャストしています。
試しに
let count = (&_ebss as usize) - ((&_sbss as *const u8) as usize);
としてみたところ
error[E0606]: casting `&u8` as `usize` is invalid
--> src\main.rs:44:17
|
44 | let count = (&_ebss as usize) - ((&_sbss as *const u8) as usize);
| ^------^^^^^^^^^^
| ||
| |help: dereference the expression: `*&_ebss`
| cannot cast `&u8` as `usize`
For more information about this error, try `rustc --explain E0606`.
めっちゃ怒られました。「`&u8`を`usize`にキャストするのは無効です」と言っています。
Type cast expressions – The Rust Referenceに記載されている通り、キャストには規則があり&TからInteger typeにはキャストできません。
そこで&T -> *T規則と*T -> Integer type規則を使ってキャストしているわけです。
また、*Tは*const Tか*mut Tだよと書かれているので
let count = ((&mut _ebss as *mut u8) as usize) - ((&mut _sbss as *mut u8) as usize);
と書いても大丈夫です。
が、欲しいのはアドレスなので_ebssのポインタがミュータブルになっても特に嬉しくありません。constで十分です。
余談ですが、欲しいのはアドレスなので(2回目)
let count = ((&_ebss as *const _) as usize) - ((&_sbss as *const _) as usize);
のように、ポインタの型を省略することもできるようです。
余談ですが(2回目)、Rustでは&(参照)と*(ポインタ)は別物として扱われているようです。
そのため、Cではあり得ない
let reference = &4;
なんてコード(4のポインタ?ではなく、4という数値が格納されているどこかの領域への参照)が書けてしまいます。ここに書かれていた例なのですが、初めて見たときは目ん玉飛び出ました。
暗黙の型変換(型強制)
コード例にあるptr::write_bytesの引数は
pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize)
のようになっています。Cのmemsetのような関数です。
Cでは特に型を意識せずに
int a[10];
memset(a, 0, sizeof(a));
とかやっているのではないでしょうか?
memsetの引数は
void *memset(void *buf, int ch, size_t n);
ですが、void *bufにint *を渡しています。
Rustでも試しに
ptr::write_bytes(&_sbss, 0, count);
としてみたところ
error[E0308]: mismatched types
--> src\main.rs:45:17
|
45 | write_bytes(&_sbss, 0, count);
| ----------- ^^^^^^ types differ in mutability
| |
| arguments to this function are incorrect
|
= note: expected raw pointer `*mut _`
found reference `&u8`
note: function defined here
--> C:\Users\user\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library/core/src/intrinsics.rs:2402:21
|
2402 | pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0308`.
めっちゃ怒られました(2回目)。「生ポインタ`*mut _`を期待しているが、`&u8`参照が見つかりました」と言っています。
次に「ミュータブルじゃないと書込みできないよねー」ということで
ptr::write_bytes(&mut _sbss, 0, count);
とミュータブルな参照にしてみたところ…ビルドが通りました?!
どういうことかというと、Type coercions – The Rust Referenceに記載されている通り、特定の状況で暗黙の型変換が行われます。
今回の場合は&mut u8 -> *mut Tなので&mut T -> *mut Tに該当します。
おわりに
知識が浅すぎてうまくまとめられませんが、まとめると
-
- Rustのキャストには規則がある
-
- Rustは&と*は区別する
- Rustにも暗黙の型変換はある
といったところでしょうか。
うまく使いこなして(あまりキャストを使わず?)Rustらしいコードを書きたいところです。できればポインタの使用は最小限に抑えたいですね。
色々実験していてエラーを出しまくりましたが、Rustのエラーは親切だなと感じました。
例えばcannot cast `&u8` as `usize` についてはFor more information about this error, try `rustc –explain E0606`.との記載があり、実行してみると
An incompatible cast was attempted.
Erroneous code example:
```
let x = &0u8; // Here, `x` is a `&u8`.
let y: u32 = x as u32; // error: casting `&u8` as `u32` is invalid
```
When casting, keep in mind that only primitive types can be cast into each
other. Example:
```
let x = &0u8;
let y: u32 = *x as u32; // We dereference it first and then cast it.
```
For more information about casts, take a look at the Type cast section in
[The Reference Book][1].
[1]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions
と例が表示されます(期待した解決策ではありませんでしたが…)。
更に[1]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressionsとの記載があり、何故エラーになったか(どう修正すればよいか)がわかった、というわけです。