「2016幎にCで曞く方法」ずいう蚘事に察する批刀


翻蚳者から

この出版物は、 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コヌドのフォヌマットに関するいく぀かの基本的なポむントを衚明したいず思いたす。


私はめったに自動曞匏蚭定ツヌルに頌りたせん。 たぶん無駄ですか

malloc䜿甚しない
calloc 。

もう1぀ありたす。 割り圓おられたメモリのすべおのビットをリセットしようずするず、非垞にarbitrary意的なプロセスになりたす。原則ずしお、これは良い考えではありたせん。 コヌドが正しく蚘述されおいる堎合、察応する倀を最初に割り圓おるこずなく、このオブゞェクトたたはそのオブゞェクトを呌び出すこずはできたせん。 callocを䜿甚するず、コヌド内のバグがれロに等しくなるずいう事実に遭遇したす。これは、システム゚ラヌを䞍芁なデヌタず混同しやすいこずを意味したす。 それはコヌドの改善のように聞こえたすか

メモリをれロ化するず、倚くの堎合、プログラムコヌドの゚ラヌが順次アルゎリズムを実行したす。 定矩により、これを正しい起動コヌスず呌ぶこずはできたせん。 ただし、順次゚ラヌの远跡ははるかに困難です。

はい、コヌドが゚ラヌなしで䜜成された堎合。 ただし、コヌドを䜜成するずきに防埡戊略に埓う堎合は、 無効なカテゎリから特定の倀を割り圓おられたメモリに割り圓おる䟡倀がありたす。

䞀方、すべおのビットをれロにするこずで蚭定されたタスクが解決される堎合は、 callocを䜿甚しおみおください。



PS
たた、来週読者をガむド付きツアヌでクラりドデヌタセンタヌに招埅したす。 Habréのむベントのお知らせはこちら 。

Source: https://habr.com/ru/post/J276611/


All Articles