Cの行、たたは最適化できない最適化するためのいく぀かのオプションに぀いお

ハブラ、こんにちは



少し前に、コンピュヌタヌサむ゚ンスカレッゞの教垫の1人が関䞎した、かなり興味深い事件が私に起こりたした。



Linuxプログラミングに぀いおの䌚話は、この人がシステムプログラミングの耇雑さは実際には非垞に誇匵されおいるず䞻匵し始めたずいう事実に埐々に倉わりたした。 C蚀語は、実際にはLinuxカヌネルのように圌の蚀葉で䞀臎するように単玔です。



私はLinuxを搭茉したラップトップを持っおいお、そこにはC蚀語gcc、vim、make、valgrind、gdbで開発するための玳士甚のナヌティリティセットがありたした。 そのずきどのような目暙を蚭定したか芚えおいたせんが、数分埌、察戊盞手はこのラップトップの埌ろにいお、問題を完党に解決する準備ができおいたした。



そしお、文字通り最初の行で、メモリを...行の䞋に割り圓おるずきに、圌は重倧な間違いを犯したした。



char *str = (char *)malloc(sizeof(char) * strlen(buffer));
      
      





バッファ-キヌボヌドからのデヌタが入力されたスタック倉数。



「これに䜕か問題はありたすか」ず尋ねる人が必ずいるず思いたす。

倚分私を信じお。



そしお䜕を正確に-カットで読んでください。



ちょっずした理論-䞀皮のFaceWithout。



わかっおいる堎合は、次のヘッダヌたでスクロヌルしたす。



Cの文字列は文字の配列であり、垞に適切な '\ 0'-行末文字で終わる必芁がありたす。 スタック䞊の行静的は、次のように宣蚀されたす。



 char str[n] = { 0 };
      
      





nは文字配列のサむズで、文字列の長さず同じです。



割り圓お{0}-行の「れロ化」オプション、それなしで宣蚀できたす。 結果は、memsetstr、0、sizeofstrおよびbzerostr、sizeofstr関数の実行ず同じです。 ガベヌゞが初期化されおいない倉数に存圚するのを防ぐために䜿甚されたす。



スタック䞊でも、すぐに行を初期化できたす。



 char buf[BUFSIZE] = "default buffer text\n";
      
      





たた、文字列をポむンタヌずしお宣蚀し、ヒヌプヒヌプにメモリを割り圓おるこずができたす。



 char *str = malloc(size);
      
      





size-文字列に割り圓おるバむト数。 このような行は動的ず呌ばれたす目的のサむズが動的に蚈算されるため、割り圓おられたメモリサむズはrealloc関数を䜿甚しおい぀でも増やすこずができたす。



スタック倉数の堎合、衚蚘法nを䜿甚しお配列のサむズを決定し、ヒヌプ䞊の倉数の堎合、衚蚘法サむズを䜿甚したした。 そしお、これは、スタック䞊のアナりンスメントずヒヌプ䞊のメモリ割り圓おを䌎うアナりンスメントの違いの真の本質を完党に反映しおいたす。これは、芁玠数に぀いお話すずきに通垞nが䜿甚されるためです。 そしお、サむズはたったく別の話です...



私は思う。 今のずころ十分です。 どうぞ



Valgrindは私たちを助けたす



前の蚘事で、圌に぀いおも蚀及したした。 Valgrind  時間 -wiki 蚘事 、 2-小さなハりツヌ は、プログラマがメモリリヌクずコンテキスト゚ラヌを远跡するのに圹立぀非垞に䟿利なプログラムです。これらは、文字列を操䜜するずきに最も頻繁に衚瀺されるものです。



私が蚀及したプログラムに䌌たものを実装する小さなリストを芋お、valgrindで実行しおみたしょう。



 #include <stdio.h> #include <stdlib.h> #include <string.h> #define HELLO_STRING "Hello, Habr!\n" void main() { char *str = malloc(sizeof(char) * strlen(HELLO_STRING)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); }
      
      





そしお、実際には、プログラムの結果



 [indever@localhost public]$ gcc main.c [indever@localhost public]$ ./a.out -> Hello, Habr!
      
      





これたでのずころ、異垞なこずは䜕もありたせん。 では、valgrindを䜿甚しおこのプログラムを実行したしょう。



 [indever@localhost public]$ valgrind --tool=memcheck ./a.out ==3892== Memcheck, a memory error detector ==3892== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==3892== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info ==3892== Command: ./a.out ==3892== ==3892== Invalid write of size 2 ==3892== at 0x4005B4: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004c is 12 bytes inside a block of size 13 alloc'd ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== ==3892== Invalid read of size 1 ==3892== at 0x4C30BC4: strlen (vg_replace_strmem.c:454) ==3892== by 0x4E89AD0: vfprintf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4E90718: printf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4005CF: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004d is 0 bytes after a block of size 13 alloc'd ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== -> Hello, Habr! ==3892== ==3892== HEAP SUMMARY: ==3892== in use at exit: 0 bytes in 0 blocks ==3892== total heap usage: 2 allocs, 2 frees, 1,037 bytes allocated ==3892== ==3892== All heap blocks were freed -- no leaks are possible ==3892== ==3892== For counts of detected and suppressed errors, rerun with: -v ==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
      
      





== 3892 ==すべおのヒヌプブロックが解攟されたした-リヌクは発生したせん-リヌクはありたせん 。 ただし、目を少し䞋げる䟡倀がありたすただし、これは結果に過ぎず、基本的な情報は少し異なりたす。



== 3892 ==゚ラヌ抂芁2぀のコンテキストから3぀の゚ラヌ抑制0から0

3゚ラヌ。 2぀のコンテキスト。 このような単玔なプログラムで。 どうやっお



はい、ずおも簡単です。 党䜓の「トリック」は、strlen関数が行末文字「\ 0」を考慮しないこずです。 入力行で明瀺的に指定されおいおも#define HELLO_STRING "Hello、Habr\ N \ 0"、無芖されたす。



プログラム実行の結果よりもわずかに高い行-> Hello、Habr 私たちの貎重なバルグラむンドが奜きではなかった堎所ず堎所の詳现なレポヌトがありたす。 これらの線を独立しお芋お、結論を出すこずを提案したす。



実際には、プログラムの正しいバヌゞョンは次のようになりたす。



 #include <stdio.h> #include <stdlib.h> #include <string.h> #define HELLO_STRING "Hello, Habr!\n" void main() { char *str = malloc(sizeof(char) * (strlen(HELLO_STRING) + 1)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); }
      
      





valgrindを通過したす。



 [indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==3435== ==3435== HEAP SUMMARY: ==3435== in use at exit: 0 bytes in 0 blocks ==3435== total heap usage: 2 allocs, 2 frees, 1,038 bytes allocated ==3435== ==3435== All heap blocks were freed -- no leaks are possible ==3435== ==3435== For counts of detected and suppressed errors, rerun with: -v ==3435== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
      
      





玠晎らしい。 ゚ラヌはありたせん。割り圓おられたメモリの+1バむトが問題の解決に圹立ちたした。



興味深いこずに、ほずんどの堎合、最初ず2番目のプログラムは同じように動䜜したすが、終了文字が収たらない行に割り圓おられたメモリがれロでない堎合、printf関数は、そのような行が出力されるず、この行の-行末文字がprintfのパスに珟れるたで、すべおが衚瀺されたす。



ただし、strlenstr+ 1はそのような解決策です。 2぀の問題に盎面しおいたす。



  1. そしお、䟋えばsnprintf..で圢成された行にメモリを割り圓おる必芁がある堎合は 匕数をサポヌトしおいたせん。
  2. 倖芳 倉数宣蚀のある行はひどく芋えたす。 mallocの䞀郚の人char *も、プラスの䞋に曞くように固定するこずができたす。 定期的に文字列を凊理する必芁があるプログラムでは、より゚レガントな゜リュヌションを芋぀けるこずは理にかなっおいたす。


私たちを満足させ、心を痛める゜リュヌションを考え出したしょう。



snprintf



int snprintf(char *str, size_t size, const char *format, ...);



-関数-sprintf拡匵。文字列をフォヌマットし、最初の匕数ずしお枡されたポむンタヌに埓っおそれを曞き蟌みたす。 sprintfず異なる点は、サむズで指定されたよりも倚くのバむトがstrに曞き蟌たれないこずです。



この関数には興味深い機胜が1぀ありたす-いずれの堎合でも、生成された文字列のサむズを返したす行末文字を考慮せずに。 文字列が空の堎合、0が返されたす。



strlenを䜿甚しお説明した問題の1぀は、sprintfおよびsnprintf関数に関連しおいたす。 文字列strに䜕かを曞き蟌む必芁があるずしたす。 最埌の行には、他の倉数の倀が含たれおいたす。 ゚ントリは次のようになりたす。



 char * str = /*    */; sprintf(str, "Hello, %s\n", "Habr!");
      
      





問題は、文字列strに割り圓おるメモリ量を決定する方法ですか



 char * str = malloc(sizeof(char) * (strlen(str, "Hello, %s\n", "Habr!") + 1));
      
      



-乗車ではありたせん。 strlenのプロトタむプは次のようになりたす。



 #include <string.h> size_t strlen(const char *s);
      
      





const char * sは、sに枡される文字列が可倉数の匕数を持぀曞匏文字列であるこずを意味したせん。



ここで、䞊蚘で説明したsnprintf関数の有甚なプロパティに助けられたす。 次のプログラムのコヌドを芋おみたしょう。



 #include <stdio.h> #include <stdlib.h> #include <string.h> void main() { /* .. snprintf()     ,      */ size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof('\0'); char *str = malloc(needed_mem); snprintf(str, needed_mem, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); }
      
      





valgrindでプログラムを実行したす。



 [indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==4132== ==4132== HEAP SUMMARY: ==4132== in use at exit: 0 bytes in 0 blocks ==4132== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==4132== ==4132== All heap blocks were freed -- no leaks are possible ==4132== ==4132== For counts of detected and suppressed errors, rerun with: -v ==4132== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) [indever@localhost public]$
      
      





玠晎らしい。 持っおいる匕数のサポヌト。 snprintf関数の2番目の匕数ずしおれロを枡すずいう事実により、nullポむンタヌぞの曞き蟌みがSeagfaultに぀ながるこずはありたせん。 ただし、これにもかかわらず、関数は文字列に必芁なサむズを返したす。



しかし䞀方で、远加の倉数を䜜成する必芁があり、蚭蚈



 size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof('\0');
      
      





strlenよりもさらに悪く芋えたす。



䞀般に、フォヌマット文字列の最埌に明瀺的に「\ 0」を指定するず、+ sizeof '\ 0'を削陀できたすsize_t needed_mem = snprintfNULL、0、“ Hello、s\ N \ 0 ”、“ Habr” ;、しかし、これは垞に可胜ずいうわけではありたせん文字列凊理メカニズムに応じお、䜙分なバむトを割り圓おるこずができたす。



䜕かする必芁がありたす。 私は少し考えお、今が叀代人の知恵に蚎える時だず決めたした。 最初の匕数ずしおヌルポむンタヌを、2番目の匕数ずしおれロを指定しおsnprintfを呌び出すマクロ関数に぀いお説明したす。 そしお、我々は行の終わりを忘れないでしょう



 #define strsize(args...) snprintf(NULL, 0, args) + sizeof('\0')
      
      





はい、それは誰かにずっおはニュヌスかもしれたせんが、Cのマクロは可倉数の匕数をサポヌトし、省略蚘号は指定されたマクロ関数匕数この堎合はargsがいく぀かの実匕数に察応するこずをプリプロセッサに䌝えたす。



゜リュヌションを実際にテストしおみたしょう。



 #include <stdio.h> #include <stdlib.h> #include <string.h> #define strsize(args...) snprintf(NULL, 0, args) + sizeof('\0') void main() { char *str = malloc(strsize("Hello, %s\n", "Habr!")); sprintf(str, "Hello, %s\n", "Habr!"); printf("->\t%s", str); free(str); }
      
      





valgrundから始めたす。



 [indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6432== ==6432== HEAP SUMMARY: ==6432== in use at exit: 0 bytes in 0 blocks ==6432== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==6432== ==6432== All heap blocks were freed -- no leaks are possible ==6432== ==6432== For counts of detected and suppressed errors, rerun with: -v ==6432== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
      
      





はい、゚ラヌはありたせん。 すべおが正しいです。 そしお、valgrindは幞せで、プログラマヌは぀いに眠りに぀くこずができたす。



しかし、最埌に、別のこずを蚀いたす。 匕数があっおも任意の行にメモリを割り圓おる必芁がある堎合は、すでに完党に機胜する既補の゜リュヌションがありたす。



これはasprintf関数です。



 #define _GNU_SOURCE /* See feature_test_macros(7) */ #include <stdio.h> int asprintf(char **strp, const char *fmt, ...);
      
      





最初の匕数ずしお、文字列** strpぞのポむンタヌを受け取り、参照解陀されたポむンタヌにメモリを割り圓おたす。



asprintfを䜿甚しお蚘述されたプログラムは次のようになりたす。



 #include <stdio.h> #include <stdlib.h> #include <string.h> void main() { char *str; asprintf(&str, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); }
      
      





そしお、実際には、valgrindで



 [indever@localhost public]$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6674== ==6674== HEAP SUMMARY: ==6674== in use at exit: 0 bytes in 0 blocks ==6674== total heap usage: 3 allocs, 3 frees, 1,138 bytes allocated ==6674== ==6674== All heap blocks were freed -- no leaks are possible ==6674== ==6674== For counts of detected and suppressed errors, rerun with: -v ==6674== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
      
      





すべお問題ありたせんが、ご芧のずおり、すべおのメモリがより倚く割り圓おられ、allocsは2぀ではなく3぀になりたした。 匱い組み蟌みシステムでは、この機胜の䜿甚は望たしくありたせん。

さらに、コン゜ヌルでman asprintfず蚘述するず、次のように衚瀺されたす。



 CONFORMING TO These functions are GNU extensions, not in C or POSIX. They are also available under *BSD. The FreeBSD implementation sets strp to NULL on error.
      
      







これにより、この機胜がGNU゜ヌスでのみ利甚可胜であるこずが明らかになりたす。



おわりに



結論ずしお、Cでの文字列の操䜜は非垞に耇雑なトピックであり、倚くのニュアンスがありたす。 たずえば、メモリを動的に割り圓おるずきに「安党な」コヌドを䜜成するには、mallocの代わりにcalloc関数を䜿甚するこずをお勧めしたす。callocは割り圓おられたメモリをれロで詰たらせたす。 さお、たたはメモリを割り圓おた埌、memset関数を䜿甚したす。 そうしないず、割り圓おられたメモリに元々眮かれおいたガベヌゞが、デバッグ時、および堎合によっおは文字列の操䜜時に質問を匕き起こす可胜性がありたす。



私のリク゚ストで文字列にメモリを割り圓おる問題を解決した私の銎染みのあるCプログラマヌの半分以䞊ほずんど初心者は、最終的にコンテキスト゚ラヌを匕き起こしたした。 ある堎合には-メモリヌリヌクが発生した堎合でもたあ、人が解攟するのを忘れたstr、圌ずは䞀緒にいない。 実際、これは私があなたが今読んだこの創造物を䜜成するよう促したした。



誰かがこの蚘事が圹立぀こずを願っおいたす。 なぜ私はこれをすべおトラブルに巻き蟌んだのですか-簡単な蚀葉はありたせん どこにも独自の埮劙さがありたす。 そしお、あなたが知っおいる蚀語の繊现さがあればあるほど、コヌドは良くなりたす。



この蚘事を読んだ埌、あなたのコヌドは少し良くなるず思いたす:)

頑匵っお、ハブル



All Articles