こんにちは。SUZUKIです。
RL78/G14用のプログラムをCS+で開発中に良く分からない現象に遭遇しました。
プログラムは
void test(char a){
char b = 0;
b = a+10;
}
という簡単なものです。
この時に変数a,bをウォッチするとアドレスがそれぞれ[L:REG][H:REG]となっており、変数bの値は0のままでした。
動作する条件としては下記3つありました。
1.変数bの宣言をグローバルで行った場合
2.別の変数(使用しない)を関数内で宣言した場合
3.変数bの型をint型にした場合
これらの時は変数a,b共にアドレスが割り当てられ正常に動作しました。
これはどのような事が起きているのでしょうか。
宜しくお願い致します。
SUZUKIさん、こんにちは。NoMaYです。再び自己フォローです。初心者向けフォーラムで、うかつなことを言ってしまったかも知れないと気になったので、補足しておこうと思います。今回、volatile云々の話をしたのは、コンパイラのオプションで最適化ありにしてコンパイル(*1)したプログラムを、やむにやまれずデバッグ(*2)しなければならなくなった状況で無理矢理に変数をウォッチ出来るようにするためのテクニックです。C言語の文法上そうしなければならない、ということは全くありません。*1: CS+で(一般的に他のデバッガでも)ソースレベルデバッグする時には最適化無しにする(fujita nozomuさん、わわいさん、アドバイス)のが原則です。*2: とは言っても、最適化無しにするとバグが発生しなくなる、こともありますので、その時は今回のウォッチのような現象も含むCS+の(一般的に他のデバッガでもの)謎な振る舞い(わわいさんアドバイス)に泣きたくなるような思いをしながら、最適化ありにしたままデバッグすることもあります。*2': ですが、正直に言うと、私は過去の投稿で、単に文章/説明を書くのが面倒だったから、という理由で手抜きをする為にvolatileを付けたことがあります、、、それと、以前にIKUZOさんという人がCC-RXの最適化で困惑していた時の投稿を読み直していて、今回の件で思い当てることがありましたので、少し調べて後で投稿します。(本日中)もうひとつ、ジェネレーションギャップ(?)を感じましたが、コンパイラのバージョンを尋ねた時にCS+のビルドプラグインのバージョンが返事として返って来たのですが、本来は何を(CS+のどこに表示されるバージョンを)返答すべきかなのか、少し確認して後で投稿します。(本日中)ちなみに、ひょっとして、個人ユーザさん(趣味としての電子工作)でしょか? (最近は、会社の先輩/上司から、もしくは新入社員教育で、教えて貰えないのかなと気になったものですから。)
SUZUKIさん、こんにちは。NoMaYです。まず、コンパイラのバージョンの確認方法ですが、以下の画面コピーのように、CS+のメニュー[ヘルプ]→[バージョン情報]もしくはメニュー[ヘルプ]→[詳細バージョン情報]、の何れかを選択します。ここまでは御存知かと思いますが、この時、プロジェクトを開いた状態であれば、以下の部分にコンパイラのバージョンが表示されます。注意が必要なのは、この時、プロジェクトを開いていない状態だと、その部分にはコンパイラのバージョンは表示されない、という点です。ちなみに、上の画面コピーはCS+ for CA,CXですが、CS+ for CCでも以下の画面コピーのように事情は同じです。プロジェクトを開いた状態であれば、以下の部分にコンパイラのバージョンが表示されます。プロジェクトを開いていない状態だと、その部分にはコンパイラのバージョンは表示されないです。後ほど、もう1つ投稿します。
SUZUKIさん、こんにちは。NoMaYです。すみません。夜遅くなるどころか日付まで変わってしまいました。CA78K0RとCC-RLでコンパイラが生成するコードを調べてみました。試したプロジェクトは以下のzipファイルに固めてあります。ただ、書いてみて思ったのですが、もっと分かり易く書き直さないと駄目な気がします。もう少し、書き方を考えてみようと思います。Issue_20171214.zip具体的には以下のソースコードに対して2×3=6つの条件でコンパイルして、コンパイルリストを出力させ、変数bへの代入がどうなっているかアセンブラコードを調べました。
void test(char a){ /* volatile */ char b = 0; b = a+10; R_DAC0_Set_ConversionValue(b);}
(1) CA78K0R V1.72(2) CC-RL V1.02 (当方特有の事情で最新版では無いです)(A) 最適化なし(B) プロジェクト作成時のデフォルトの最適化(C) プロジェクト作成時のデフォルトの最適化で変数bをvolatile宣言今回の件は(1-B)に該当するのですが、わわいさんがアドバイスしている通り、最適化のため変数への値の代入がなされなかった、ということです。ただ、ちょっと紛らわしいことに、もう少しCA78K0Rが賢ければ多分なされなかったと思われる「0を代入する」ことは行われており、それに対して「a+10を代入する」ことだけが行われなくなっていたので、ということでした。(ちなみに、CC-RLでは「0を代入する」ことも行われなくなっていました。)(1) CS+ for CA,CX V4.00.01 + CA78K0R V1.72(1-A) 最適化なし ⇒ 0(00H)や変数a+10(0AH)を変数bへ代入している
760 760 ; line 83 : void test(char a){ 761 761 00012 _test: 763 763 00012 C7 push hl ;[INF] 1, 1 764 764 00013 C1 push ax (補)引数aをスタックに移す ;[INF] 1, 1 765 765 00014 C1 push ax (補)変数bの領域をスタック上に確保する ;[INF] 1, 1 766 766 00015 FBF8FF movw hl,sp ;[INF] 3, 1 768 768 ; line 84 : /* volatile */ char b = 0; 770 770 00018 CC0100 mov [hl+1],#00H ; b,0 ;[INF] 3, 1 771 771 ; line 85 : b = a+10; 773 773 0001B 8C02 mov a,[hl+2] ; a ;[INF] 2, 1 774 774 0001D 0C0A add a,#0AH ; 10 ;[INF] 2, 1 775 775 0001F 9C01 mov [hl+1],a ; b ;[INF] 2, 1 776 776 ; line 86 : R_DAC0_Set_ConversionValue(b); 778 778 00021 318E shrw ax,8 (補)AレジスタをAXレジスタ(引数)に移す ;[INF] 2, 1 779 779 00023 RFC000000 call !!_R_DAC0_Set_ConversionValue ;[INF] 4, 3 780 780 ; line 87 : } 783 783 00027 1004 addw sp,#04H ;[INF] 2, 1 784 784 00029 C6 pop hl ;[INF] 1, 1 785 785 0002A D7 ret ;[INF] 1, 6
(1-B) プロジェクト作成時のデフォルトの最適化 ⇒ 0(00H)を変数bへ代入しているが、変数a+10(0AH)を変数bへ代入していない
759 759 ; line 83 : void test(char a){ 760 760 00012 _test: 762 762 00012 C7 push hl ;[INF] 1, 1 763 763 00013 16 movw hl,ax ;[INF] 1, 1 765 765 ; line 84 : /* volatile */ char b = 0; 767 767 00014 5700 mov h,#00H ; 0 ;[INF] 2, 1 768 768 ; line 85 : b = a+10; 770 770 00016 66 mov a,l ;[INF] 1, 1 771 771 00017 0C0A add a,#0AH ; 10 ;[INF] 2, 1 772 772 ; line 86 : R_DAC0_Set_ConversionValue(b); 774 774 00019 318E shrw ax,8 (補)AレジスタをAXレジスタ(引数)に移す ;[INF] 2, 1 775 775 0001B RFC000000 call !!_R_DAC0_Set_ConversionValue ;[INF] 4, 3 776 776 ; line 87 : } 779 779 0001F C6 pop hl ;[INF] 1, 1 780 780 00020 D7 ret ;[INF] 1, 6
(1-C) プロジェクト作成時のデフォルトの最適化で変数bをvolatile宣言 ⇒ 0(00H)や変数a+10(0AH)を変数bへ代入している
759 759 ; line 83 : void test(char a){ 760 760 00012 _test: 762 762 00012 C7 push hl ;[INF] 1, 1 763 763 00013 C1 push ax (補)引数aをスタックに移す ;[INF] 1, 1 764 764 00014 C1 push ax (補)変数bの領域をスタック上に確保する ;[INF] 1, 1 765 765 00015 FBF8FF movw hl,sp ;[INF] 3, 1 767 767 ; line 84 : volatile char b = 0; 769 769 00018 CC0100 mov [hl+1],#00H ; b,0 ;[INF] 3, 1 770 770 ; line 85 : b = a+10; 772 772 0001B 8C02 mov a,[hl+2] ; a ;[INF] 2, 1 773 773 0001D 0C0A add a,#0AH ; 10 ;[INF] 2, 1 774 774 0001F 9C01 mov [hl+1],a ; b ;[INF] 2, 1 775 775 ; line 86 : R_DAC0_Set_ConversionValue(b); 777 777 00021 8C01 mov a,[hl+1] ; b ;[INF] 2, 1 778 778 00023 318E shrw ax,8 (補)AレジスタをAXレジスタ(引数)に移す ;[INF] 2, 1 779 779 00025 RFC000000 call !!_R_DAC0_Set_ConversionValue ;[INF] 4, 3 780 780 ; line 87 : } 783 783 00029 1004 addw sp,#04H ;[INF] 2, 1 784 784 0002B C6 pop hl ;[INF] 1, 1 785 785 0002C D7 ret ;[INF] 1, 6
(2) CS+ for CC V6.00.00 + CC-RL V1.02(2-A) 最適化なし ⇒ 0(0x00)や変数a+10(0x0000A)を変数bへ代入している
00000009 121 _test:00000009 122 .STACK _test = 600000009 127 ;*** 84 : void test(char a){00000009 C7 129 push hl (補)引数aと変数bの領域をスタック上に確保する0000000A 9801 130 mov [sp+0x01], a (補)引数aをスタックに移す0000000C 131 ;*** 85 : /* volatile */ char b = 0;0000000C C80000 133 mov [sp+0x00], #0x000000000F 135 ;*** 86 : b = a+10;0000000F 8801 137 mov a, [sp+0x01]00000011 318E 138 shrw ax, 8+0x00000 (補)AレジスタをAXレジスタ(作業用)に移す00000013 040A00 139 addw ax, #0x000A00000016 60 140 mov a, x00000017 9800 141 mov [sp+0x00], a00000019 143 ;*** 87 : R_DAC0_Set_ConversionValue(b);00000019 8800 145 mov a, [sp+0x00]0000001B FC000000 146 call !!_R_DAC0_Set_ConversionValue0000001F C6 147 pop hl00000020 D7 148 ret00000021 149 ;*** 88 : }
(2-B) プロジェクト作成時のデフォルトの最適化 ⇒ 0(0x00)も変数a+10(0x0A)も変数bへ代入していない
00000009 113 _test:00000009 114 .STACK _test = 400000009 119 ;*** 84 : void test(char a){00000009 0C0A 121 add a, #0x0A0000000B 122 ;*** 85 : /* volatile */ char b = 0;0000000B 123 ;*** 86 : b = a+10;0000000B 124 ;*** 87 : R_DAC0_Set_ConversionValue(b);0000000B EC000000 126 br !!_R_DAC0_Set_ConversionValue0000000F 127 ;*** 88 : }
(2-C) プロジェクト作成時のデフォルトの最適化で変数bをvolatile宣言 ⇒ 0(0x00)や変数a+10(0AH)を変数bへ代入している
00000009 113 _test:00000009 114 .STACK _test = 600000009 119 ;*** 84 : void test(char a){00000009 C7 121 push hl (補)変数bの領域をスタック上に確保する0000000A 122 ;*** 85 : volatile char b = 0;0000000A C80000 124 mov [sp+0x00], #0x000000000D 125 ;*** 86 : b = a+10;0000000D 0C0A 127 add a, #0x0A0000000F 9800 128 mov [sp+0x00], a00000011 129 ;*** 87 : R_DAC0_Set_ConversionValue(b);00000011 8800 131 mov a, [sp+0x00]00000013 FC000000 132 call !!_R_DAC0_Set_ConversionValue00000017 C6 133 pop hl00000018 D7 134 ret00000019 135 ;*** 88 : }
SUZUKIさん、こんにちは。NoMaYです。今回の件、アセンブラコードが全く分からなくても、わわいさんがアドバイスしているような、最適化のため変数への値の代入がなされなかった、ということの雰囲気が分かるような説明を考えてみました。今回の件はCC78K0Rだったのですが、インパクトが強そうな気がしましたので、説明としてはCC-RLを使うことにしました。まず、SUZUKIさんが書かれたコードはもともと以下のものでした。
void test(char a){ char b = 0; b = a+10; R_DAC0_Set_ConversionValue(b);}
でも、よくよく考えると、b = 0 としたすぐ後に b = a+10 としていますので、プログラムの機能としては b = 0 は無くても良く、以下のコードで置き換えることが出来ます。
void test(char a){ char b; b = a+10; R_DAC0_Set_ConversionValue(b);}
そうして、コードの置き換えを考え始めると、わざわざ a+10 を b に代入しなくても、以下のコードで置き換えることが出来ることに気付きます。
void test(char a){ R_DAC0_Set_ConversionValue(a+10);}
ところで、敢えて置き換える価値はありませんが、下のように置き換えてもプログラムの機能としてはもともとのコードと等価です。ただ、この置き換えから、今回の件でウォッチで見た時に b = a+10 が実行されていないように見えた原因が何となく分かるのではないかと、思います。(人間が見れば b = 0 がプログラムの機能として意味が無いことは直ぐに分かりますが、コンパイラとしては少なくとも「a+10 を b に代入した後にその b を読み出して引数にセットする」という冗長な処理が無くなったので価値はある、だから置き換える、と判断することもあるということです。)
void test(char a){ char b = 0; R_DAC0_Set_ConversionValue(a+10);}
ちなみに、CC-RLのデフォルトの最適化では、上の4つのコードは全て以下の同じアセンブラコードにコンパイルされました。(アセンブラコードの意味が分からなくても同じコードが生成されていることが伝われば良いかな、と考えました。) ソースは違うが、プログラムの機能としては同じなので、同じアセンブラコードになる、という訳です。
00000009 120 _test:00000009 114 .STACK _test = 400000009 126 ;*** 84 : void test(char a){00000009 0C0A 128 add a, #0x0A0000000B 129 ;*** 85 : char b = 0;0000000B 130 ;*** 86 : b = a+10;0000000B 131 ;*** 87 : R_DAC0_Set_ConversionValue(b);0000000B EC000000 133 br !!_R_DAC0_Set_ConversionValue0000000F 134 ;*** 88 : }
00000009 113 _test:00000009 114 .STACK _test = 400000009 119 ;*** 84 : void test(char a){00000009 0C0A 121 add a, #0x0A0000000B 122 ;*** 85 : char b;0000000B 123 ;*** 86 : b = a+10;0000000B 124 ;*** 87 : R_DAC0_Set_ConversionValue(b);0000000B EC000000 126 br !!_R_DAC0_Set_ConversionValue0000000F 127 ;*** 88 : }
00000009 113 _test:00000009 114 .STACK _test = 400000009 119 ;*** 84 : void test(char a){00000009 0C0A 121 add a, #0x0A0000000B 122 ;*** 85 : R_DAC0_Set_ConversionValue(a+10);0000000B EC000000 124 br !!_R_DAC0_Set_ConversionValue0000000F 125 ;*** 86 : }
00000009 113 _test:00000009 114 .STACK _test = 400000009 119 ;*** 84 : void test(char a){00000009 0C0A 121 add a, #0x0A0000000B 122 ;*** 85 : char b = 0;0000000B 123 ;*** 86 : R_DAC0_Set_ConversionValue(a+10);0000000B EC000000 125 br !!_R_DAC0_Set_ConversionValue0000000F 126 ;*** 87 : }
あと、プログラムの機能という点では、上のCC-RLのデフォルトの最適化ありでコンパイルしたアセンブラコードと、CC-RLで最適化無しでコンパイルした以下のアセンブラコードは、なんと等価ということになるのです。(アセンブラコードの意味が分からなくても量も内容も全然違うコードが生成されていることが伝われば良いかな、と考えました。)
00000009 121 _test:00000009 122 .STACK _test = 600000009 127 ;*** 84 : void test(char a){00000009 C7 129 push hl0000000A 9801 130 mov [sp+0x01], a0000000C 131 ;*** 85 : char b = 0;0000000C C80000 133 mov [sp+0x00], #0x000000000F 135 ;*** 86 : b = a+10;0000000F 8801 137 mov a, [sp+0x01]00000011 318E 138 shrw ax, 8+0x0000000000013 040A00 139 addw ax, #0x000A00000016 60 140 mov a, x00000017 9800 141 mov [sp+0x00], a00000019 143 ;*** 87 : R_DAC0_Set_ConversionValue(b);00000019 8800 145 mov a, [sp+0x00]0000001B FC000000 146 call !!_R_DAC0_Set_ConversionValue0000001F C6 147 pop hl00000020 D7 148 ret00000021 149 ;*** 88 : }
[余談]以前にウェブで調べていて気付いたのですが、CC-RLは情報処理学会で論文が発表されていて、その論文は優秀論文の1つとして表彰されたらしいです。ipsj.ixsq.nii.ac.jp/ej/?action=pages_view_main&active_action=repository_view_main_item_detail&item_id=163721&item_no=1&page_id=13&block_id=8RL78マイコン向けCコンパイラCC-RLにおける機種依存最適化の設計The Design of Target Dependent Optimizations in CC-RL, an Optimizing C Compiler for the RL78 Microcontroller(情報処理学会の有料コンテンツです。2018年06月06日から無料でダウンロード可能になるみたいです。)www.ipsj.or.jp/award/yamashita2016.html2016年度(平成28年度)山下記念研究賞 受賞者/論文タイトル/発表研究会略称