web-dev-qa-db-ja.com

プログラミングでマクロを使用して、コードをより高速、効率的、コンパクトにする方法

最近、私は世界で最高の競争力のあるプログラマーのソリューションのいくつかを経験していました。私はそれらの人々がプログラムを書くときに、できればC++でテンプレートを使用していることを知りました。

最初に_#define_とtypedefを使用して独自のキーワードを作成するため、これらのプログラムを理解するのは困難です。これにより、コードがよりコンパクトになり、より速く記述できるようになります。

for()ループのように、コードの記述中に何度も繰り返される多くの命令があります。したがって、長い命令を書く代わりに、マクロを作成できます。コードをよりコンパクトにするために、他のステートメントでも同じことができます。

_#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define SC1(x)          scanf("%lld",&x)
#define SC2(x,y)        scanf("%lld%lld",&x,&y)
#define SC3(x,y,z)      scanf("%lld%lld%lld",&x,&y,&z)
#define PF1(x)          printf("%lld\n",x)
#define PF2(x,y)        printf("%lld %lld\n",x,y)
#define PF3(x,y,z)      printf("%lld %lld %lld\n",x,y,z)
#define REP(i,n)        for(long long i=0;i<(n);i++)
#define FOR(i,a,b)      for(long long i=(a);i<=(b);i++)
#define FORD(i,a,b)     for(long long i=(a);i>=(b);i--)
#define WHILE(n)        while(n--)
#define MEM(a, b)       memset(a, (b), sizeof(a))
#define ITOC(c)         ((char)(((int)'0')+c))
#define MID(s,e)        (s+(e-s)/2)
#define SZ(a)           strlen(a)
#define MOD             1000000007
#define MAX             10000000005
#define MIN             -10000000005
#define PI              3.1415926535897932384626433832795
#define TEST(x)         printf("The value of \"%s\" is: %d\n",#x,x)

const int INF = 1<<29;

typedef long long ll;
typedef unsigned long long ull;

#define FILEIO(name) \  freopen(name".in", "r", stdin); \   freopen(name".out", "w", stdout);

_

C/C++コンパイラのプリプロセッサがこれらのマクロをすべて処理する方法に興味があります。私は次のようなマクロを使用して定義されている定数を理解しています:

_#define MOD    10000000007
#define MAX    10000000005
#define MIN    -10000000005
#define PI     3.1415926535897932384626433832795
_

しかし、次のように、命令を参照できる関数マクロを作成するにはどうすればよいですか。

_#define SC1(x)          scanf("%lld",&x)
#define SC2(x,y)        scanf("%lld%lld",&x,&y)
#define SC3(x,y,z)      scanf("%lld%lld%lld",&x,&y,&z)
#define PF1(x)          printf("%lld\n",x)
#define PF2(x,y)        printf("%lld %lld\n",x,y)
#define PF3(x,y,z)      printf("%lld %lld %lld\n",x,y,z)
#define REP(i,n)        for(long long i=0;i<(n);i++)
#define FOR(i,a,b)      for(long long i=(a);i<=(b);i++)
#define FORD(i,a,b)     for(long long i=(a);i>=(b);i--)
#define WHILE(n)        while(n--)
#define MEM(a, b)       memset(a, (b), sizeof(a))
#define ITOC(c)         ((char)(((int)'0')+c))
#define MID(s,e)        (s+(e-s)/2)
#define SZ(a)           strlen(a)
#define TEST(x)         printf("The value of \"%s\" is: %d\n",#x,x)
_

上記のようなマクロを定義することで、プログラマーの生産性、効率、速度を向上させることができますよね?

今私の質問は:

  1. 次のようなマクロを定義すると、
_#define SC1(x)          scanf("%lld",&x)
_

コンパイラーがこのマクロを処理する方法。より一般的には、プログラムのコンパイル/実行時にこの命令がプリプロセッサによってどのように理解されるか。

  1. ループの3つの異なるバージョンを参照するために使用される3つの異なる種類のマクロがあります。
_#define REP(i,n)        for(long long i=0;i<(n);i++)
#define FOR(i,a,b)      for(long long i=(a);i<=(b);i++)
#define FORD(i,a,b)     for(long long i=(a);i>=(b);i--)
_

この命令では、最初に計算する必要がある変数値を括弧_()_で囲みます。マクロを操作するときの括弧の意味は何ですか?

  1. 次の命令の_\_の意味:
_#define FILEIO(name) \  freopen(name".in", "r", stdin); \   freopen(name".out", "w", stdout);
_

私はC/C++でのマクロの使用に関連する幅広い質問を知っています。誰かがソースコードで遭遇したときにこれらのマクロ命令を解釈/理解する方法について正しい方向を私に指摘できれば、私ができるように、それを本当に感謝します独自のマクロを変更して記述し、より高速で効率的なコーディングを行います。

1
strikersps

コードの文字数がコード品質の最も重要なメトリックである、またはテンプレートがコードをより速く書くための最良の方法であるという根本的な概念に同意しないので、これはフレームチャレンジになります。

免責事項:これは(主に)C#開発者の観点から書かれていますが、私がしている議論は言語にとらわれません。


これらのプログラムは、最初に#defineとtypedefを使用して独自のキーワードを作成するため、理解が困難です。

これがexact理由で、これらのテンプレートが嫌いです。それらは、それらのテンプレートを作り出した開発者以外の開発者の可読性を大幅に損なう。基本的な言語のキーワードをより好きな名前に書き換えることは、何の価値もない主観的な聖なる戦争です。

その上、短縮すると、構成可能性が低下します。例えば。 forループは、提供することを目的とする機能セットを提供するために必要なだけ冗長です。冗長にしない場合は、機能セットの一部を省略します。

forループは、外部カウンターを定義し、そのカウンターを使用して終了条件をチェックするwhileループの圧縮バージョンです。それは合理的に可能な限り簡潔です。


これは、コードをよりコンパクトにするために行われます...

コードの記述に必要な文字数でコード品質を測定しようとすることは、自動車の構造に組み込まれた部品の量で自動車の性能を測定することに似ています。

私はこれを十分に強調することはできません:読みやすさは毎回簡潔さよりも優先されます。 KLOC(または文字数)は、コード品質の有効な尺度ではありません。コンパクトなコードは、開発者が理解しづらくする一方で、コンパイラーにまったく影響を与えない(パフォーマンスの向上はない)ため、一般的に悪いコードです。

これは、コードの長さやコースの長さに上限がないという意味ではありません。しかし、言語構造を圧縮しようとすることは合理的ではありません。

読みにくいコードのバリアントは、読みやすいコードよりもパフォーマンスが向上する可能性のある例外があります。しかし、基本的な言語構造の使用例は、これらの周辺の例の1つではありません。


...そしてより速く書くことができます。

これを処理する方法は他にもあります。 IDEを使用すると、言語のキーワードを再定義する必要なしに、コードの記述を迅速化できます。

Visual Studioの例として、forと入力してTabキーを2回押すと、すべてが書き込まれます。

enter image description here

さらにタブ補完を使用すると、3つの操作(初期化、終了条件、増分条件)をすばやく書き込むことができます。

これは良いですか?どれどれ:

  • タブの完成は、それが筋肉のメモリになるために習得するのに少し時間がかかりますが、テンプレートを使用することも習得するのに時間が必要です(新しい開発者がテンプレートを学ぶ必要があるため)。それに慣れるまでには、ほぼ同じ時間がかかります。
  • 異なるコードベースは異なるテンプレートを定義する可能性がありますが、IDEで作業する場合、作業しているコードベースに関係なく同じ機能が得られます。
  • 結局のところ、タブ補完を使用すると、任意に作成されたテンプレート名に依存しないため、結果のコードは読みやすくなります。任意の定義を知る必要はなく、コード自体を読み取ることができます。

私は、任意に決定された定義ではなく、タブ補完などのIDEツールを使用することに関して、大きな勝利としてそれを呼びかけています。


また、付記として:

#define SC1(x)          scanf("%lld",&x)
#define PF3(x,y,z)      printf("%lld %lld %lld\n",x,y,z)
//...

これらのほとんどは通常のメソッドとして記述でき、ここでテンプレートを使用する理由はありません。単純なメソッドを記述しないようにテンプレートを誤用する領域にあなたは流れ込んでいるようです。


あなたのコメントに応じて

私はプログラミングコンテストについて言及しています。正しい心の誰もが業界でそのようなコードを望まない理由を教えてください

同じ理由で、食料品の買い物をするためにレースカーを運転する人はいません。レーサーカーは、レーサーと自動車メーカーの間の競争のために構築されていますが、レーサーカーは、大多数の自動車所有者にとって有用な日常の自動車ではありません。

同様に、業界コードは開発者間の課題として書かれているのではなく、タスクを実行し、必要に応じて適応できるように保守可能に書かれています。

テンプレート化されたコードから得られる限界速度の向上(秒の端数)は、何時間もの無駄な作業につながる可能性があるコードの可読性の低下によって相殺されます。

さらに、テンプレートは、1人の開発者が1回限りのコードベースを作成するシナリオで機能する場合があります。しかし、複数の開発者が同じコードベースに再度アクセスする必要がある場合、これは耐えられません。

15
Flater

上記のようなマクロを定義することにより、プログラマーの生産性、効率、速度を向上させることができます。

それは誤りです。あなたは自分で書いた「それらのプログラムを理解するのが難しい」-したがって、これらのマクロはコードを読んで理解するための生産性を向上させませんでした。

でも、直接質問にお答えしましょう。

#define SC1(x) scanf("%lld",&x)のようなマクロを定義すると、コンパイラはこのマクロをどのように処理しますか?

実際には、処理を行うのはCまたはC++コンパイラ(少なくともその主要部分ではありません)ではなく、これを行う preprocessor というコンポーネントです。簡単に言えば、ここのプリプロセッサは、実際のコンパイルが始まる前に、いくつかのテキスト置換を行います。たとえば、上記のマクロ定義の後、プリプロセッサは次のようなコード行を置き換えます

_ SC1(foo);
_

沿って

_ scanf("%lld",&foo);
_

これが、メインコンパイラが最終的に表示するコードです。

2つ目の質問は次のとおりです。

マクロを書くときの括弧の意味は何ですか?

プリプロセッサはテキスト処理のみを行うため、マクロの変数が評価される前に、マクロが展開されます。つまり、引数が最初に評価され、結果が関数内で処理される通常の関数呼び出しのように結果が意味的に動作することを確認するには、括弧が必要です。

たとえば、これらの余分な括弧がない場合、次のような行

_ `REP(i, 1|2)`
_

に拡張されます

_ for(long long i=0;i< 1|2 ;i++)
_

これはfor(long long i=0;i< (1|2) ;i++)とは異なるセマンティクスを持っています。実際、括弧がないと、これは無限ループを導入する難読化された方法になります。なぜなら、_i< 1|2_は、 iの値。これは、「<」の前に「|」よりも高い演算子があるためです。 CおよびC++。

そしてあなたの3番目の質問へ:

次の説明での\の意味:

_#define_構文では、通常、マクロ定義全体が同じ行にあると想定しています。複数行にまたがるには、行の最後にバックスラッシュ「\」を追加する必要があります。しかし、あなたの例では、同じ行のそれらのステートメント間のバックスラッシュはまったく効果がなく、おそらく省略できます。

8
Doc Brown

このようなマクロは、コードが記述されているが、誰もコードを読み取ったり維持したりすることのない競争力のあるプログラミングに役立ちます。それは書かれ、それが機能することを証明するために実行され、それから(本質的に)捨てられます。コードが維持されないという事実を考えると、初期開発の速度のみを考慮して最適化するのが妥当です。

ただし、通常のコードは、何年も維持されます。その間、通常は実際にコードを書き込むよりも、コードを読み取るのに(かなり)多くの時間を費やします。これらのようなマクロは、読み取りの(非常に)費用で書き込み用に最適化されるため、競合プログラミング以外では、ほぼ間違いなく正味の損失です(そして、それはかなり大きな損失です)。

3
Jerry Coffin

マクロを使用すると、コードがエラーが発生しやすくなり、判読不能になり、保守が困難になります。
言語の専門家や開発者からの標準的な推奨はマクロを避けることであり、ほとんどの人はそれに同意しています。

マクロでできることは、コードを密に(=読み取り不可能)にすることです。そのため、ターゲットがソースファイルのディスク領域を節約する(->コードゴルフを参照)場合、または特定のコンテストに勝つ場合、マクロは役立ちますが、fastおよびefficientの場合、影響はありません。コードが適切に記述されていて、コンパイラーが優れている場合、コードは高速です。マクロはプリコンパイラによって展開され、これにはまったく影響しません。
コードwritingは、マクロを使用するとより高速に見えるかもしれませんが、特定のレベルの複雑さ、つまり約1画面分に達するまでのみです。

リストしたマクロの多くは数十年前のコーディングスタイルであるため、ソースが15年古くなっている可能性があります。たとえばprintfには、C++ 07以降の推奨オプションがあります。

2
Aganju