翻訳者から:
この出版物は、 Inoventica Servicesブログで記事「How to C in 2016」の翻訳が出版された後に自発的に発生したシリーズの3番目の最終記事です。 ここで、原文で述べられた論文のいくつかが批判され、最初の出版物の著者が提起した質問に関する意見の最終的な「写真」とC.でのコードの記述方法が最終的に形成されます。 私が思うに、 CodeRushユーザーの多くの馴染みのあるユーザーによって与えられたテキストへのヒントである2番目の出版物はここにあります 。
マット(少なくとも私の知る限り、著者の姓が示されていないWebサイト)は、「2016年のCでのプログラミング」という記事を公開しました。これは後にRedditとHacker Newsに掲載されました。
はい、Cプログラミングを際限なく「議論する」ことができますが、私は明らかに反対する側面があります。 この重要な記事は建設的な議論で書かれています。 場合によっては、マットが正しい可能性があり、私は間違っています。
Mattの出版物全体を引用するわけではありません。 特に、私が同意するいくつかの点を省略することにしました。 始めましょう。
Cでのプログラミングの最初のルールは、他のツールで問題がなければ使用しないことです。
私はこの声明に同意しませんが、これは議論するには広すぎるトピックです。
Cでプログラミングする場合、lang
はデフォルトでC99を参照するため、追加のオプションは必要ありません。
clang
のバージョンによって異なります
clang 3.6
デフォルトでC99で動作し、
clang 3.6
はC11で動作します。 箱から出して使用するときにどれほど厳しいものかわかりません。
gccまたはclangに特定の標準を使用する必要がある場合は、複雑にしないで、std = cNN -pedanticを使用してください。
デフォルトでは、gcc-5
は-std=gnu11
要求しますが、実際にはGNUなしでc99またはc11を指定する必要があります。
原則として、これらの目的に非常に適している特定のgcc拡張機能を使用したくない場合を除きます。
新しいコードでchar
、int
、short
、long
またはunsigned
ようなものを見つけた場合、ここにエラーがあります。
もちろんすみませんが、これはナンセンスです。 特に、intは現在のプラットフォームで最も受け入れられる整数データ型です。 少なくとも16ビットの高速符号なし整数について話している場合、intを使用しても何も問題はありません(または、
int_least16_t
オプションを参照できます。これは、同じタイプの関数で問題
int_least16_t
ますが、
int_least16_t
の方が価値があります)。
最新のプログラムでは、#include <stdint.h>
を指定してから、標準のデータ型を選択する必要があります。
int
スペルが
«std»
はないという事実は、非標準的なものを扱っているという意味ではありません。
int
、
long
などのタイプはC言語に組み込まれ、
<stdint.h>
修正されたtypedefは追加情報として後で表示されます。 これは、組み込みの型よりも「標準」ではありませんが、いくつかの点では後者より劣っています。
float
-32ビット浮動小数点標準
double
-64ビット浮動小数点標準
float
および
double
は、32ビットおよび64ビット浮動小数点標準の非常に一般的なIEEEタイプです。特に、最新のシステムでは、Cでプログラミングするときにこれにこだわることはありません。私は、floatが64ビットで使用されるシステムで作業しました。
注意してください:これ以上のchar.
通常、Cプログラミング言語では、char
コマンドは呼び出されるだけでなく、誤って使用されます。
残念ながら、Cでプログラミングするときにパラメーターとバイトをマージすることは避けられません。 char型は安定して1バイトと見なされます。「バイト」は少なくとも8ビットです。
ソフトウェア開発者は、符号なしのバイト操作が実行される場合でも、charコマンドを使用して「バイト」を意味し続けます。 個々の符号なしバイト/オクテット値にuint8_t
を指定し、符号なしバイト/オクテット値のシーケンスにuint8_t *
を選択する方がはるかに正確です。
バイトが含まれている場合は、
unsigned char
使用します。 オクテットの場合、
uint8_t
選択します。
CHAR_BIT > 8
場合、
uint8_t
を作成できません。つまり、機能せず、コードをコンパイルできません(おそらくこれがまさに必要なものです)。 8ビット以上のオブジェクトを
uint_least8_t
場合は、
uint_least8_t
使用し
uint_least8_t
。 バイトがオクテットを意味する場合、次のようなコードをコードに追加します。
#include <limits.h> #if CHAR_BIT != 8 #error "This program assumes 8-bit bytes" #endif
注:POSIXは
CHAR_BIT == 8
要求します。
Cプログラミング言語では、文字列リテラル("hello")
はchar *
ように見えます。
いいえ、文字列リテラルはchar []に設定されます。 特に、「hello」の場合、char [6]です。 配列はポインターではありません。
unsigned
を使用してコードを記述しようとしないでください。 これで、コンテンツを読みにくくするだけでなく、完成品を使用する効率性に疑問を投げかける多数のデータ型を使用して、厄介なCの規則なしにまともなコードを記述する方法がわかりました。
Cの多くの型には、いくつかの単語で構成される名前が付けられます。 それに問題はありません。 余計な文字を入力するのが面倒な場合、これはあらゆる種類の略語をコードに詰め込む必要があるという意味ではありません。
単純なuint64_t
制限できる場合、誰がunsigned long long intを入力しますか?
一方では、intを意味するunsigned long longを使用できます。 同時に、これらは異なるものであり、
unsigned long long
型は少なくとも64ビットであり、インデントが存在する場合と存在しない場合があります。
uint64_t
、インデントビットなしで、正確に64ビット用に設計されています。 このタイプは、必ずしもいずれかのコードに登録されているわけではありません。
unsigned long long
Cの組み込み型です。このプログラミング言語を使用する専門家は、この言語に精通しています。
または、
uint_least64_t
試して
uint_least64_t
。これは、
unsigned long long
と同一または異なる場合があります。
タイプ<stdint.h>
、より具体的かつ正確な意味を持ち、作者の意図をよりよく伝え、コンパクトであり、操作と読みやすさの両方にとって重要です。
もちろん、
intN_t
型と
uintN_t
型
uintN_t
より具体的です。 しかし、すべてのコードが主なものではありません。 あなたにとって重要ではないものを指定しないでください。
uint64_t
は、実際に正確に64ビットが必要な場合にのみ選択して
uint64_t
。
特定の形式に適応する必要がある場合など、正確な長さの型が必要になることがあります(バイト順、要素の配置などに重点が置かれることがあります。Cの<stdint.h>は、特定のパラメーターを記述する機能を提供しません)。 ほとんどの場合、組み込み型[u] int_leastN_tまたは[u] int_leastN_tが適している特定の範囲の値を指定するだけで十分です。
この場合のポインターの正しいタイプはuintptr_t
で、ファイル<stdint.h>
によって設定されます。
なんというひどい間違い。
小さなエラーから始めましょう:
uintptr_t
は
<stddef.h>
ではなく
<stddef.h>
<stdint.h>
に設定されます。
これは、詳細についての話です。 データを失うことなく
void*
を別の整数型に変換できないコマンドを呼び出すことは、
uintptr_t
によって決定されることはほとんどありません(そのような場合は、非常にまれです)。
代わりに:
long diff = (long)ptrOld - (long)ptrNew;
はい、そうではありません。
使用:
ptrdiff_t diff = (uintptr_t)ptrOld - (uintptr_t)ptrNew;
しかし、このオプションは優れています。
型の違いを強調したい場合は、次のように書きます。
ptrdiff_t diff = ptrOld - ptrNew;
バイトに集中する必要がある場合は、次のようなものを選択します。
ptrdiff_t diff = (char*)ptrOld - (char*)ptrNew;
ptrOld
および
ptrNew
が必要なパラメーターを示さない場合、または単にオブジェクトの最後からジャンプする場合、ポインターがデータ減算コマンドを呼び出す方法をトレースすることは困難です。
uintptr_t
切り替えると、少なくとも相対的な結果が保証されますが、非常に有用とは言えません。 ポインターを使用した比較またはその他の算術は、高レベルシステム用のコードを記述する場合にのみ許可されます。そうでない場合は、調査中のポインターが特定のオブジェクトの末尾を参照するか、そこからスキップすることが重要です(例外:==および!異なるオブジェクト)。
そのような状況では、intptr_t(プラットフォーム上の単語に等しい値に対応する整数データ型)を参照するのが合理的です。
しかし、ありません。 「単語と等しい」という概念は非常に抽象的なものです。
intptr_t
データ損失なしで
void*
を
intptr_t
、またはその逆に正常に変換
intptr_t
符号付き整数型です。 さらに、これは
void*
を超える値になる場合があります。
32ビットプラットフォームでintptr_t
、intptr_t
int32_t
変換されます。
それは起こりますが、常にではありません。
64ビットプラットフォームでは、intptr_t
はint64_t
ます。
繰り返しになりますが、必要ではありません。
実際、size_t
は「巨大な配列インデックスを格納できる整数」のようなものです。
いや
そのため、作成中のプログラムの偏りの印象的な指標を修正できます。
はい、このタイプのデータを使用すると、プログラムの起動に関係する最大のオブジェクトのサイズに関する情報を保存できます(これもオプションであるという意見もありますが、実際には、これがまさに起こると仮定することができます)。 すべてのオフセットが同じオブジェクト内で作成される場合、メインメモリオフセットを修正できます。
いずれの場合でも、最新のプラットフォームでは、size_t
はuintptr_t
と実質的に同じ特性を持つため、32ビットバージョンでsize_t
、size_t
uint32_t
、64ビットuint64_t
はuint64_t
ます。
最も可能性が高いが、必要ではない。
より具体的には、
size_t
は個々のオブジェクトのサイズを保存するために使用できますが、
uintptr_t
はポインター値を設定し、それに応じてさまざまなオブジェクトのバイトのアドレスを混同しなくなります。 最新のシステムのほとんどは、分割できないアドレス行で動作するため、理論的には、オブジェクトの最大サイズは合計メモリサイズに等しくなります。 Cプログラミング標準では、この要件を厳守する必要があります。 そのため、たとえば、64ビットシステム上でオブジェクトが32ビットを超えない場合があります。
「モダン」という言葉を強調し、古い選択肢(x86など、ニアポインターとファーポインターを使用したセグメント化されたアドレス指定を使用)を自動的に省略し、C標準との互換性を提供する可能性のある将来の製品については触れていませんが、定義を超えています「モダン」
操作中はデータ型を参照しないでください。 常に適切なタイプのポインターを使用してください。
これは選択肢の1つですが、成功する唯一のソリューションではありません(そして、確かに、「%p」にvoid *を記述する必要があることに同意するでしょう)。
ポインターの初期値は%pです(最新のコンパイラーでは、16進システムで表示されます。最初はポインターをvoid *
送信します)
素晴らしいヒント-出力形式のみが起動オプションによって設定されます。 これは通常16進値ですが、他に何も指定されていないとは思わないでください。
printf("Local number: %" PRIdPTR "\n\n", someIntPtr);
someIntPtr
という名前は
int*
型を意味し、実際には
intptr_t
型を指定します。
トピックにはさまざまなバリエーションがあります。つまり、マクロ名の無限の組み合わせを覚える必要はありません。
some_signed_type n; some_unsigned_type u; printf("n = %jd, u = %ju\n", (intmax_t)n, (uintmax_t)u);
intmax_t
と
uintmax_t
は通常64ビットです。 それらの変換は、物理的なI / Oよりもはるかに経済的です。
注:%はフォーマット文字列のリテラル本体に入りますが、タイプポインターはその外側に留まります。
これらはすべてフォーマット文字列の一部です。 マクロは、隣接する文字列リテラルと組み合わせた文字列リテラルとして定義されます。
最新のコンパイラーは#pragma once
サポートし#pragma once
しかし、このディレクティブを使用する必要があると言う人はいません。 プロセッサの命令でさえ、そのような推奨事項を公開していません。 また、「一度だけの見出し」セクションでは、#pragma onceについての言葉ではありません。 ただし、
#ifndef
説明されています。 次のセクションでは、「#ifndefパッカーの代替案」が#pragmaを1回点滅させましたが、この場合、これは移植性のあるオプションではないことに注意してください。
この関数は、すべてのコンパイラおよび異なるプラットフォームでサポートされており、ヘッダーセキュリティコードを手動で入力するよりもはるかに効率的なメカニズムです。
そして、誰がそのような勧告をしますか?
#ifndef
ディレクティブは理想的ではないかもしれませんが、信頼性と移植性があります。
重要:構造に内部インデントがある場合、{0}メソッドはこの目的のために追加のバイトをゼロにしません。 そのため、たとえば、構造が1ワード単位で埋められるため、構造物のcounter
後(64ビットプラットフォーム上)に4バイトのインデントがある場合に発生します。 未使用のインデントバイトを含む構造全体を無効にする必要がある場合は、memset(&localThing, 0, sizeof(localThing))
指定しますmemset(&localThing, 0, sizeof(localThing))
sizeof(localThing) == 16 bytes
、8 + 4 = 12バイトしか使用できません。
タスクは複雑になっています。 通常、インデントバイトに特別な注意を払う理由はありません。 貴重な時間を彼らに捧げたい場合は、
memset
を使用してリセットしてください。
memset
を使用して構造体をクリーニングすると、要素全体に実際にゼロの値が割り当てられていることを考慮しても、浮動小数点型またはポインターに対して同じ効果が保証されないことに注意してください-それぞれ0.0および
NULL
である必要があり
NULL
(ただしほとんどのシステムでは、関数は正常に動作します)。
可変長の配列はC99で登場しました
いいえ、C99はVLA(可変長配列)の初期化子を提供しません。 しかし、実際、MattはVLAイニシャライザーについては書いておらず、VLA自体についてのみ言及しています。
可変長の配列は矛盾した現象です。 mallocとは異なり、リソース割り当てにエラー検出は含まれません。 したがって、データのバイト数Nを割り当てる必要がある場合、以下が必要になります。
{ unsigned char *buf = malloc(N); if (buf == NULL) { /* allocation failed */ } /* ... */ free(buf); }
少なくとも、概して、次の場合よりも安全です。
{ unsigned char buf[N]; /* ... */ }
はい、VLAの使用時のエラーには深刻な問題が伴います。 しかし、実際には、どのプログラミング言語の各機能についても同じことが言えます。
そして、固定長の古い配列では、同様の疑問が生じました。 配列を作成する前にサイズを確認する限り、変数Nを持つVLAは同じサイズの固定長配列と同じくらい無害です。 原則として、固定長の配列を記述するには、実際のデータを格納するためにその一部が必要なので、予想される要素の数を超える値が選択されます。 VLAを使用すると、コンポーネントが必要とするスペースを正確に割り当てることができます。 そして、ここで私はマットの推奨に同意します。
1つの側面に加えて、C11では、必要に応じてVLAを選択できます。 実際、ほとんどのC11コンパイラーは、小さな組み込みシステムの場合を除き、可変長配列をオプションとして認識しているとは思いません。 ただし、最もポータブルなコードを作成する場合は、この機能を覚えておく必要があります。
関数が*任意**ソースデータおよび特定の長さで機能する場合、このパラメーターのタイプを制限しないでください。 *
知って間違い:
void processAddBytesOverflow(uint8_t *bytes, uint32_t len) { for (uint32_t i = 0; i < len; i++) { bytes[0] += bytes[i]; } }
代わりに使用します:
void processAddBytesOverflow(void *input, uint32_t len) { uint8_t *bytes = input; for (uint32_t i = 0; i < len; i++) { bytes[0] += bytes[i]; } }
void*
、任意のメモリのパラメーターを修正するための理想的な型であることに同意します。 少なくとも標準ライブラリの
mem*
関数を使用します(ただし、lenは
uint32_t
ではなく
size_t
にする必要があり
uint32_t
)。
ソースデータ型をvoid *として宣言し、関数本体で直接必要な実際のデータ型を再割り当てまたは再度参照することにより、ライブラリで何が起こっているかを考える必要がないため、ユーザーを保護します。
小さなメモ:これは、Mattの機能では詳しく説明されていません。 ここでは、
void*
から
uint8_t*
への暗黙的な変換を確認します。
この例では、一部の読者がアライメントの問題に直面しています。
そして、彼らは間違っていました。 バイトシーケンスのように、特定のメモリを使用する場合、常に安全です。
C99は、関数<stdbool.h>
セット全体を提供します。ここで、true
は1、false - 0
です。
はい。ただし、
_Bool
組み込み型のエイリアスとして使用される
bool
を指定することもできます。
成功/失敗の戻り値の場合、関数はtrue
またはfalse
を返す必要があり、戻り値の型int32_t
なく、1と0の手動入力を必要とします(さらに悪いことに1と-1。それをどうやって調べるか:0-success
、および1-failure?
または0-success
、および-1-failure?
))。
特に、Unixなどのシステムでは、成功した場合に関数が0を返し、失敗した場合にゼロ以外の値(多くの場合-1)を返す広範なアルゴリズムがあります。 多くの場合、ゼロ以外の変数の結果はさまざまな種類のエラーを示します。 既製のインターフェースに新しい関数を追加する場合、前述の標準に従うことが重要です(一般に、関数が効果的に機能するためのオプションは1つだけなので、0は成功に相当しますが、多くのエラーが発生する可能性があります)。
特定の条件を分析するために作成された関数は、
true
または
false
を返す必要があり
false
。 それらを成功/失敗したコード実行結果と混同しないでください。
bool
関数には、アサーションの形式で名前を付ける必要があります。 英語では、はい/いいえの質問に答える文言になります。 たとえば、
is_foo()
および
has_widget()
特定のアクション用に設計された関数で、実行の成功度を知ることが重要な場合、おそらく別のステートメントによって設定されます。 一部の言語では、例外の追加/減算に頼ることが合理的です。 Cでは、関数の肯定的な結果にゼロ値を設定するなど、特定の暗黙のルールに従う必要があります。
2016年にCで開発された製品をフォーマットできる唯一の製品はclang-formatです。 ネイティブのclang形式の設定は、他の自動Cコードフォーマッタよりも桁違いに高くなっています。
私自身はclang形式を使用していません。 私は彼に会う必要があります。
しかし、Cコードのフォーマットに関するいくつかの基本的なポイントを表明したいと思います。
- 行末に開き括弧を入れます。
- タブの代わりにスペースを使用します。
- 1レベルの4列。
- 中括弧はすべてです(個々の場合を除き、読みやすさを向上させるために、タスクを行に直接リストする方が簡単です)。
- 作業しているプロジェクトの指示に従います。
私はめったに自動書式設定ツールに頼りません。 たぶん無駄ですか?
malloc
使用しない
calloc
。
もう1つあります。 割り当てられたメモリのすべてのビットをリセットしようとすると、非常にarbitrary意的なプロセスになります。原則として、これは良い考えではありません。 コードが正しく記述されている場合、対応する値を最初に割り当てることなく、このオブジェクトまたはそのオブジェクトを呼び出すことはできません。
calloc
を使用すると、コード内のバグがゼロに等しくなるという事実に遭遇します。これは、システムエラーを不要なデータと混同しやすいことを意味します。 それはコードの改善のように聞こえますか?
メモリをゼロ化すると、多くの場合、プログラムコードのエラーが順次アルゴリズムを実行します。 定義により、これを正しい起動コースと呼ぶことはできません。 ただし、順次エラーの追跡ははるかに困難です。
はい、コードがエラーなしで作成された場合。 ただし、コードを作成するときに防御戦略に従う場合は、 無効なカテゴリから特定の値を割り当てられたメモリに割り当てる価値があります。
一方、すべてのビットをゼロにすることで設定されたタスクが解決される場合は、
calloc
を使用してみてください。
PS
また、来週読者をガイド付きツアーでクラウドデータセンターに招待します。 Habréのイベントのお知らせはこちら 。