【C言語】
sprintfで文字列連結しよう
読みにくいstrcatは使わない

strcpy+ strcat👇 sprintf

■sprintfで文字列連結すると読みやすい

#include <stdio.h>

int main(void){
    char    *p1 = "123" ;
    char    *p2 = "456" ;
    char    *p3 = "789" ;
    char    csv[1024];

    sprintf(csv,"%s,%s,%s",p1,p2,p3);
    puts(csv);
}

実行結果をイメージしやすいです。

➡実行結果

./a.out
123,456,789

strcpy+strcatをダラダラ書くと読みにくい

#include <stdio.h>
#include <string.h>

int main(void){
    char    *p1 = "123" ;
    char    *p2 = "456" ;
    char    *p3 = "789" ;
    char    csv[1024];
    
    strcpy(csv,p1)  ;
    strcat(csv,",") ;
    strcat(csv,p2)  ;
    strcat(csv,",") ;
    strcat(csv,p3)  ;
    puts(csv);
}

実行結果をイメージしにくいです。


■sprintfにはバッファオーバーフローの危険性がある

#include <stdio.h>
int main(void){
    char    dst[32] ;
    char *src = 
    "Jugemu Jugemu," 
    "Gokō no Surikire," 
    "Kaijarisuigyo no Suigyōmatsu," 
    "Ungyōmatsu," 
    "Fūraimatsu," 
    "Kuunerutokoro ni Sumutokoro," 
    "Yaburakōji no Burakōji," 
    "Paipo Paipo," 
    "Paipo no Shūringan," 
    "Shūringan no Gūrindai," 
    "Gūrindai no Ponpokopī no Ponpokona no Chōkyūmei,"
    "Chōkyūmei no Chōsuke";

    sprintf(dst,"%s\n",src);
    puts(dst);
}

出力バッファは充分に大きくとりましょう。
このコードでは
領域破壊で異常終了するかもしれません。


■sNprintfには尻切れの危険性がある

#include <stdio.h>
int main(void){
    char    dst[32] ;
    char *src = 
    "Jugemu Jugemu," 
    "Gokō no Surikire," 
    "Kaijarisuigyo no Suigyōmatsu," 
    "Ungyōmatsu," 
    "Fūraimatsu," 
    "Kuunerutokoro ni Sumutokoro," 
    "Yaburakōji no Burakōji," 
    "Paipo Paipo," 
    "Paipo no Shūringan," 
    "Shūringan no Gūrindai," 
    "Gūrindai no Ponpokopī no Ponpokona no Chōkyūmei,"
    "Chōkyūmei no Chōsuke";

    snprintf(dst,sizeof(dst),"%s",src);
    puts(dst);
}

➡実行結果

./a.out
Jugemu Jugemu,Gokō no Surikire

寿限無落語が途中で終わってしまいました。
お客に怒られないでしょうか?

意味のある文字列を
途中でぶった切っても良い状況って
筆者には余り思いつきません。

例えば
”やっぱり買うのを止めた”を途中で切ると
”やっぱり買う”になり
意味が反対になってしまいます。

それでもsnprintfは安全でしょうか?


自作itoa(INT_MIN)はバグるのでsprintfを使う

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>
#include <string.h>

// itoa 関数の実装
char* itoa(int num) {
    // 変数の初期化
    static char result[256]; // 最大 12 桁の整数を扱えるように
    int index = 0;
    bool is_negative = false;

    // 負の数を扱う
    if (num < 0) {
        is_negative = true;
        num = -num;
    }

    // 各桁を文字に変換して結果に追加
    while (num > 0) {
        int digit = num % 10;
        result[index++] = '0' + digit;
        num /= 10;
    }

    // 負の符号を追加
    if (is_negative) {
        result[index++] = '-';
    }

    // 文字列を反転
    for (int i = 0; i < index / 2; ++i) {
        char temp = result[i];
        result[i] = result[index - i - 1];
        result[index - i - 1] = temp;
    }

    // 文字列の終端を設定
    result[index] = '\0';

    return result;
}
int     main(void){
    //INT_MAXは動く
    int max = INT_MAX;
    printf("%d\n%s\n",max,itoa(max));

    //INT_MINでバグる   
    int min = INT_MIN;
    printf("%d\n%s\n",min,itoa(min));
}

生成AIにitoa関数を作ってもらいました。


➡暴走結果の例

./a.out
2147483647
2147483647
-2147483648
-

自作 itoa(INT_MIN)で暴走しました。
ネットで紹介されている
自作itoaもほぼ全滅です。


➡sprintfを使った修正例

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

int     main(void){
    char    ascii[256];
     
    //INT_MAXは動く
    int max = INT_MAX ;
    sprintf(ascii,"%d",max);
    printf("%d\n%s\n",max,ascii);

    //INT_MINも動く
    int min = INT_MIN ;
    sprintf(ascii,"%d",min);
    printf("%d\n%s\n",min,ascii);
}

itoa関数を自作しないで
標準関数のsprintfを使いました。


➡期待する結果が得られる

./a.out
2147483647
2147483647
-2147483648
-2147483648

つまり、
itoa関数を自作しないで
sprintfを使えという話でした。

参考:

C-FAQ 13.1

■sprintfは入力出力引数を同じにしてはいけない

#include <stdio.h>
int main(void){
    char    dst[16] = "abcd";
    char    src[16] = "1234";

    sprintf(dst,"/%s/%s/",src,dst);
    puts(dst);
}

dstが入力と出力の引数に使われているので
未定義の動作(バグ)となります。

参考:

軽率にも次のようなコードを使っているプログラムがある。