CC-RLでちょっと改善して欲しい案件

NoMaYさん、シェルティさん、エビスクラウンです。

NoMaYさんのご指摘通り(もっともなご指摘なので)、別スレッドにしました。

本件はCC-RLへの改善提案です。CC-RLで標準ライブラリのva_startを使うと警告が表示されてしまいます。

具体的には、以下のようなリストです。

#include <stdarg.h>

void sub(int n, ...)
{
va_list ap;

      va_start( ap, n );

これをビルドすると、「W0520549:変数 "ap" は値が設定される前に使用されました。」となります。

上記はva_startの基本的な使用例であり、va_startを使う以上、必ず上記のようになります。

それなのに「警告って?」。そもそもva_startは関数呼出しではなく、関数呼出しマクロなので、何故、マクロ展開後のリストで警告を判断してくれないのだろうか?と疑問に思います。

これだとユーザが自ら作成した関数呼出しマクロの中にも、引数を代入式の左辺値としていたら、同じ警告が表示されると思います。

是非、改善をお願いしたいのですが、如何でしょうか?

  • エビスクラウンさん

    シェルティです。こんにちは。

    ありがとうございます。コンパイラの人に聞いてみたいと思います。

    シェルティも変だと思いました。

    以上です

  • エビスクラウンさん、シェルティさん、こんにちは。NoMaYです。

    va_startの件、私、ある程度は分かります。ミソは、va_startマクロが展開された後のCソースは、CC-RLやCC-RX(やCC-RHも?)では、単に別の名前のビルトイン関数に置き換えられているだけで、apの実引数がそのままそのビルトイン関数に渡されているだけなのです。そして、そのビルトイン関数は非常に特殊な特徴を持っていて、C言語でありながら、(いわゆるcall by valueでは無く) いわゆるcall by reference(っポイもの)で引数が引き渡されているのです。(もっとも、C++言語であれば、call by referenceは言語仕様の範囲内ですので、どうこう特別なことでは無いとも言えますけれど。) また、回避策は割と簡単です。(ですが、Amazon FreeRTOSのソースのように、オープンソースの塊で、かつ、その中の、他から持ってきたソース内で発生していると、う~~~ん~~~どうしよう、と悩むことになります。(というか悩みました。(実体験です。)))

    以下、CC-RLのインクルードファイルのソースでの話です。

    CS+/CC/CC-RL/V1.10.00/inc/stdarg.hでの定義

    typedef char __near *va_list;
    void __near __builtin_va_start(va_list);
    #define va_start(_ap,_parmN)  __builtin_va_start((_ap))

     
    もとのCソース

    void sub(int n, ...)
    {
    va_list ap;

          va_start( ap, n );

     
    マクロ展開後のCソース

    void sub(int n, ...)
    {
    va_list ap;

          __builtin_va_start((ap)); ← このapはcall by valueでは無く実はcall by referenceとなっている

     
    以下、回避策です。va_start呼び出し前に ap に NULL を代入しておきます。

    void sub(int n, ...)
    {
    va_list ap = NULL;

          va_start( ap, n );

     
    なお、va_startが特殊な特徴をもっているのはルネサスコンパイラだけではなくLLVMも似たようになっています。(GCCもそうだったかも、、、) ただ、LLVMではワーニングは出ません。(GCCも出てなかった。) そこがルネサスコンパイラと違いますね。

    LLVM/RL78/10.0.0.202107/lib/clang/LLVM for Renesas RL78 10.0.0.202107/include/stdarg.h

    #ifndef _VA_LIST
    typedef __builtin_va_list va_list;
    #define _VA_LIST
    #endif
    #define va_start(ap, param) __builtin_va_start(ap, param)
    #define va_end(ap)          __builtin_va_end(ap)
    #define va_arg(ap, type)    __builtin_va_arg(ap, type)

     
    [関連リンク]

    今回、探してみて以下のQiitaの記事がありました。

    @kurashoが2020年11月07日に更新
    va_list、可変長引数の仕組みを理解してvprintf関数を使う
    qiita.com/kurasho/items/1f6e04ab98d70b582ab7
    本文中ではなく下の方の他の人からのコメントがポイントのところです。

    lo48576 @lo48576 2017-07-09 23:24

      どこかにva_listの定義等はCPUなどで変わるって書いてあった

    引数がどのように関数に渡されるかは呼出規約によって違うので、それに合わせて定義も変わります。
    たとえば引数を最初のものから順番にスタックに積んでいく方式の呼出規約を採用したCPUやOSであれば、この記事の例のように、va_listはスタックを指す単なるポインタで済みます。

    しかし、たとえばx86_64のLinuxなどではこのようになっており、最初のいくつかの引数だけレジスタに載せて残りがスタック、しかも整数か浮動小数点数かでどのレジスタに載せるか変わる、などのルールがあります。(Windowsのx86_64でも似たようなものっぽいです。)
    このような場合、va_argは最初のいくつかの引数はレジスタから、残りはスタックからというように少々面倒なルールに従って値を得る必要があるため、va_listも単純なポインタというわけにはいかなくなります。
    (たとえば、整数の引数のインデックス、浮動小数点数の引数のインデックス、スタックのポインタといった情報を持つことになります。参考: c - What is the format of the x86_64 va_list structure? - Stack Overflow)

    そんなわけで、最近の(特に64bitとかマルチプラットフォームとかに対応する)コンパイラでは、va_listやva_arg等の実際の動作はコンパイラビルトイン(組込み関数)で隠蔽されていることが多いことと思います。


  • NoMaYさん、こんばんは、エビスクラウンです。

    私の認識は若干違います。va_listやva_argはprintf関数やscanf関数等の可変個の引数を操作するためのものであり、可変個の引数は必ずスタックに格納される、が私の認識です(x86_64を知らないので私が間違っているかも知れません)。そうでないと可変個の引数取り出しが、むちゃくちゃ大変なことになると思いますが。。。うーーむ。違うのだろうか?

    ちなみに可変個の引数がスタックでないとすると、μT-Kernelのサブシステムの拡張SVCハンドラは、C言語では記載できなくなってしまうのです。今回の提案の発端も、そこにあるものですから。。。

     

    まぁ、それはそうとして、話を元に戻しますと、結果がビルドインだろうと何だろうと、言語仕様に記載された記述に対して警告を表示するのは、明らかに間違っていると思っています。

    「ルール通りに記載したのに間違っていると怒られた」、警告が出た時は、そんなやるせない気持ちになりました。ちなみに現状の対策はNoMaYさんと同じ、無意味な初期設定を行って回避しています。

     

  • エビスクラウンさん、こんにちは。NoMaYです。

    > 可変個の引数は必ずスタックに格納される

    今回調べて気付いたのは、もう随分昔に(AMDの64bitアーキテクチャが出た時には)世の中はそんなシンプルなものでは無くなっていた、ということですね。しかも、以下のQiitaの記事のようにバリエーションすらも存在する、ということでした。もっとも、他の記事も読むと、結局どのようなものであれ(どのように複雑になっていても)、コンパイラが面倒を見てくれますので、stdarg.hのマクロを適切に使用していれば、可変個引数の取り扱いで困ることは無いですよ、という話の流れでしたね。

    幸い、CC-RXやCC-RL(やCC-RHも?)では、可変個引数の扱いはエビスクラウンさんがイメージしていたシンプルなもののままのようです。

    逆に、私が感じたのは、シンプルなもののままだったので va_list を __builtin_va_list を導入せず char * のままにしていたことにより、変なワーニングが出るようになってしまっていたのかなぁ、とも思いました。ただ、ダミーの初期化でワーニングを回避することがそれなりに行われてしまっていて、もう導入出来ない(導入するとワーニング回避コードがエラーになってしまう?)ような事態になっていないか、ちょっと気掛かりでもあります。

    また、問題の遠因に、たぶん最初にこういうことになったと思われるCC-RX V2は、ワーニングレベルを上げないとワーニングが表示されない、かつ、ワーニングレベルを上げるやり方が分かり難くて上げていない人がほとんど?、ということもあるのではないかなぁ、と私は思っていたりします。(ルネサスさん自身も上げずに使ってますね。)

    [関連リンク]

    x86-64とARM64の可変長引数関数の呼び出し規約
    qiita.com/hotpepsi/items/bd1f496411a2df74b704
     

  • NoMaYさん、こんにちは。エビスクラウンです。

    貴重な情報を有難う御座います。

    【可変個の引数】

    認識を改めました。確かに全てがスタックでなくても、va_argで面倒見てくれるのであれば、問題ないですね!

    また、NoMaYさんのご懸念は逆です。μT-Kernelの元のソースコードが拡張SVCハンドラの可変個引数をポインタでアクセスしていたのを、私がva_argを使った方が移植性があると思い改造したところ、今回の問題が発生しました。従いまして、va_argの利用は間違っていなかったと自画自賛している次第です。

    【インフォメーションレベル】

    RXで試してみました。確かにCC-RXでもインフォメーションを有効にすると出ました。それにしもインフォメーションレベルがOSのポーティングに役立つとは思いませんでした。潜在的なミスを結構発見できました。まぁ、OSのソースコードなので回避できないメッセージも沢山ありますが、そうでないものも結構ありましたので、これらを全て潰してからアップロードしようかと思っています。

    有難う御座いました。

  • ヱビスクラウンさん

    シェルティさんから連絡をもらった中の人です。
    va_startの警告はご指摘の通り、不適切ですね。明確な時期は申し上げられませんが、出ないように修正したいと思います。ご指摘頂きありがとうございます!

    なお、NoMaYさんからのご説明の通り、va_startのマクロ展開後、引数は左辺値になるわけではありませんので
    ユーザが自ら作成した関数呼出しマクロの中にも、引数を代入式の左辺値としていたら、同じ警告が表示される

    は発生していないと考えています。認識が違えば再度ご連絡いただければと思います。

  • va_startだけの問題だったのですね。了解しました。

    今回のように個人ユーザから提案も拾って頂けると嬉しい限りです。今後とも、よろしくお願い致します。