お世話になります。SYOUです。
RX62Tでソフトを作っていて、作成したコードにアドレスが割り当てられず、ブレークポイントを置けなくて困っているので投稿させてもらいます。(開発環境はCS+になります)
下に簡単に関数を書いたのですがこの関数をCS+でビルドしてみたところPos=i;のところにアドレスが表示されず、ブレークポイントが設置できません。
関数の内容をほかのところに直接書いた場合にはアドレスも表示され、ブレークポイントの設定もできました。
似たような状況になったことのある方いらっしゃいましたらお知恵をお借りしたいです。
void f(uint_16 j ,uint_8 k ,uint_32 Pos ){
uint_16 i;
for(m=0; j<10; m++){
if( (box[ m ]==j) && (k==1) ){
Pos = i;
}
わわいです。
Posという変数が代入されるだけで参照されないため、コンパイラの最適化でその命令が削除されてますね。
#また、変数 i に値が入ってません
これをどーにかするには、お使いのコンパイラの設定で、最適化を「なし」にするか、「デバッグ用」、にするかしてコンパイルし直してみればいいです
void f(uint_16 j ,uint_8 k ,uint_32 Pos ){ uint_16 i; for(m=0; j<10; m++){ if( (box[ m ]==j) && (k==1) ){ Pos = i; } } }
CS+ の挙動以前の話として、書かれてるプログラムの見直しをお勧めします。
どのようにアセンブラに変換されたのか見たらどうですか。
わわいさん
最適化を「なし」にしてコンパイルをしたら治りました。ありがとうございました。
fujitaさん
簡単にしようとして大部分を削ったつもりだったのですが、そのせいでよくわからないソフトになってしまっていました。すいません。
Posの型はそのままなので参照型への変更を考えたいと思います。
リカルドさん
混合状態で確認してみたのですがよくわからないところに飛んでしまっていて理解できなかったので、何かヒントになればと思い、質問させてもらいました。
(アセンブラが詳しくないせいなのですが…)
追加の質問になりますが、最適化をそのままの状態にして治す方法などはあるのでしょうか?
なるべくならば今までの最適化レベルでプログラムを進めていきたいと考えているのでもしできるのであれば教えていただきたいです。
> 最適化をそのままの状態にして治す方法などはあるのでしょうか?
Pos が参照型になっていれば最適化が有効でも削除されることはありません。
SYOUさん、こんにちは。
議論を
(1)このプログラム特有の挙動
(2)最適化の一般論
の2つに分けて考えてはどうだろうかと思いました。
(1)に関しては藤田様の回答が正にそれだと思います。
(2)に関してはわわい様の回答に関連する話で、言葉の定義からして、最適化を掛けるということは無駄なコードを削除する、
ということでもありますので、削除されてデバッグし難くなるのであれば最適化を掛けないようにする、しかないです。一般的に
良く行われているのは、プログラマさんのPC上でのデバッグ時のみ最適化を掛けないでデバッグして、評価仕様書に基づく
単体検査なり結合検査なりは最適化を掛けた状態でビルドしたプログラムで行う方法です。
逆に、初心者のうちは、最適化はなしにしといたほうがいいです。
さもないと、自分の思うように動いてくれない場合が出てきて、ムダに時間を取られることになったりします。
最適化をONにしたところで、ゆーほど動作速度が早くなったりするもんでもないですし、最適化で削れるオブジェクトサイズなんか、いまどきでは問題にもならないですしねえ。
fujita nozomuさん
>Pos が参照型になっていれば最適化が有効でも削除されることはありません。
これは、ルネサスコンパイラなら正しいのですが、GCCなんかだと正しくなくなったりします。
過去にこれについての議論があったので、検索していただければでてくるかとおもいます。
まー、最適化の可否についてはいろいろ議論が分かれるところではありますが。。
gcc version 4.8.0.201603-GNURX (GCC_Build_20160903) にて正しい結果となることを確認しました。
$ cat -n test.cpp 1 typedef unsigned char uint_8; 2 typedef unsigned short uint_16; 3 typedef unsigned long uint_32; 4 extern uint_8 box[]; 5 6 void f(uint_16 j ,uint_8 k ,uint_32 Pos ){ 7 uint_16 i; 8 for(i=0; i<10; i++){ 9 if( (box[ i ]==j) && (k==1) ){ 10 Pos = i; 11 } 12 } 13 } 14 15 void g(uint_16 j ,uint_8 k ,uint_32& Pos ){ 16 uint_16 i; 17 for(i=0; i<10; i++){ 18 if( (box[ i ]==j) && (k==1) ){ 19 Pos = i; 20 } 21 } 22 } $ rx-elf-gcc -O2 -g -c test.cpp ; rx-elf-objdump -S test.o test.o: file format elf32-rx-le Disassembly of section P: 00000000 <__Z1fthm>: typedef unsigned char uint_8; typedef unsigned short uint_16; typedef unsigned long uint_32; extern uint_8 box[]; void f(uint_16 j ,uint_8 k ,uint_32 Pos ){ 0: 02 rts 00000001 <__Z1gthRm>: Pos = i; } } } void g(uint_16 j ,uint_8 k ,uint_32& Pos ){ 1: fb e2 00 00 00 00 mov.l #0, r14 7: 5f 11 movu.w r1, r1 00000009 <.LBB2>: uint_16 i; for(i=0; i<10; i++){ if( (box[ i ]==j) && (k==1) ){ 9: 5b 22 movu.b r2, r2 b: fc 3b e5 not r14, r5 e: 0a bra.s 18 <.LBB2+0xf> } } void g(uint_16 j ,uint_8 k ,uint_32& Pos ){ uint_16 i; for(i=0; i<10; i++){ f: 74 0e 00 00 00 00 cmp #0, r14 15: 20 1b beq.b 30 <.LBB2+0x27> 17: 03 nop if( (box[ i ]==j) && (k==1) ){ 18: fd 38 e4 movu.b [r14+], r4 1b: 47 14 cmp r1, r4 1d: 21 f2 bne.b f <.LBB2+0x6> 1f: 61 12 cmp #1, r2 21: 21 ee bne.b f <.LBB2+0x6> 23: ff 24 e5 add r14, r5, r4 Pos = i; 26: e3 34 mov.l r4, [r3] } } void g(uint_16 j ,uint_8 k ,uint_32& Pos ){ uint_16 i; for(i=0; i<10; i++){ 28: 74 0e 00 00 00 00 cmp #0, r14 2e: 21 ea bne.b 18 <.LBB2+0xf> if( (box[ i ]==j) && (k==1) ){ Pos = i; } } } 30: 02 rts 31: fd 70 40 00 00 00 80 nop ; max #0x80000000, r0 $
ページ右上の検索で「GCC」や「GNU」を検索してみましたがそれらしい議論は見当たりませんでした。それ以上の確認はしてません。
SYOUさん
「最適化される」という挙動は全て、「最適化できる根拠がある」からされる、と思って下さい。例えば以下のプログラム
①int a,b;
②a = 2;
③b = 5
④a = 20;
こんなカンジで①から④まで実行されるプログラムがある場合。④でaに20が入るなら、その間にaが使われてないから、②のa=2は無駄だろ!俺が消してやるわ!という親切心が、「最適化」です。
ここで問題になるのが、③のあたりでブレークポイント貼ってICEとかで値を見たい場合。②が消されてるので、ICEで見るとaは「不定値」だったりします。こうなるとワケが判らなくなり、環境がおかしいとかマイコンバグだとか変な議論になります。
また、割り込みなどで取得した値をGLOBAL変数に保存しておき、これをメインループで処理する、等の手法を用いる場合がありますが、これも最適化では消されたりします。割り込み内で「参照されない変数」とコンパイラが判断したら、じゃあ無駄だから消すね、と消してくれちゃいます。
なぜ最適化で消されるか、という理屈を理解すれば、「最適化に振り回されない作り方」が出来るようになりますよー。
すみません。ポカをやってしまいましたので消しました。大変申し訳ありません。
デバッガーで何をデバッグするのかを明確にした方が良いと思われます。皆さんがデバッガーに期待するものに少し興味があります。
メモリーや実行時間の節約はコンパイラーに任せて、設計書を満たす安全性や移植性や検証性が高いソースコードが第一です。
最適化ありとなしで同じ結果が得られることは大前提です。あまり考えずに、低コストのためにリリース用およびテスト用は最適化ありで、ソースコードと対応させるためにデバッグ用は最適化なしで良いと思います。
最適化ありとなしで同じ結果が得られることは理想ですが実際そうとも限らないのでデバグも最適化ありで行うのが望ましいと思います。
最適化なしでビルドした場合にはコンパイラが警告しない変数未初期化のバグや無意味なコードの削除など、最適化ありでは不具合の発見に役立つことも多いです。
CやC++のコンパイラはプロ用の道具であり書いたソースから出力されるコードが想像できないレベルでは使用すべきツールではないとも思います。
CC-RX使ってます。最適化の有無で速度は雲泥の差です(定量的な比較はあえて避けます)。CAのRL78なんかは最適化してもほとんど速くならないですね。
プログラムの修正が許されて、ピンポイントで止めたいところが分かってるなら、nop() 関数を入れて、そこにブレーク置きます。ほとんどこれでいけます。-schedule のオプションつけると、ステップ実行は厳しいですね。
最適化する/しない については、最適化しないとCPU時間足らない場合は、選択肢無いですよね。-schedulerだけやめて動いてくれれば、少しだけステップで追えたりします。ロカール変数のウォッチできないのも辛かったりしますね。
(投稿後、行間修正しました)
このスレッドの始まりがソースレベルデバッグでしたので無意識的に書き忘れてしまいましたが、
ソースレベルデバッグと双璧を成すもう1つのデバッグ方法としてprintfデバッグがありますね。
通常、printfデバッグしか使えない時には、わざわざ最適化無しに切り替えることはしないですね。
(ソースレベルデバッグと併用する時は横着(?)して最適化無しのまますることも私はありますが。)
私はWindowsで当たり前にC/C++ソースレベルデバッグが行われていた頃にプログラミングを
学んだので、デバッグと聞くと『パブロフの犬』的にソースレベルデバッグをイメージしてしまいます。
(Arduinoやmbedを触ることもありますのでprintfデバッグもするのですが、どうにもこうにも、
デバッグのデフォルトとしてはソースレベルデバッグをイメージしてしまいます。) ちなみに、
ArduinoもmbedもC++ですね。(Arduinoは少し味付けされていますが。) というか、マイコンの
値段のことまでそれなりに考慮に入れる場合は、今でもC/C++以外に選択肢がないですよね、、、