[C言語] strlen, strcat, strncat, strcpy, strncpy

文字列の結合とかコピーとかの関数についてのメモ

strlen

文字列の長さを取得する。

//
// strlen は NULL文字 が見つかるまでの文字列長を返す
//
printf("%zd\n", strlen("A")); // 1
printf("%zd\n", strlen("abc\0def")); // 3

/* エスケープシーケンスの罠 */
printf("%zd\n", strlen("\0123456789")); // 8
/*
 UTF8の場合
 ※UTF8はnull文字以外に0バイトは仕様上出てこない。
 ※UTF8が壊れている場合は不正なnull値が出現する可能性あり。
 */
printf("%zd\n", strlen("一二三")); // 9
strlen(NULL); // Segmentation Fault

strcat

catは猫ではなくconcatenate。バッファオーバーランに注意

const int bufferSize = 20;
char buffer[bufferSize];
memcpy(buffer, "ABCDE", sizeof("ABCDE"));
printf("%ld", sizeof("ABCDE"));
puts((const char *)buffer); // "ABCDE" ※よくないキャスト

strcat(buffer, "0123456789");

puts((const char *)buffer); // "ABCDE0123456789"

const char *text = "Buffer Over Flow?";
if (strlen(buffer) + strlen(text) < bufferSize) {
  strcat(buffer, text); // ここは通らない
} else {
  puts("バッファを超えるため処理しなかったよ");
}
char buffer[100];
buffer[0] = '\0';
const char *text = "abcde\0ABCDE";

// 戻り値は buffer のポインタ
char *returnValue = strcat(buffer, text);

puts((const char *)buffer); // "abcde"
puts(returnValue == buffer ? "YES" : "NO"); // "YES"
char buffer[10];

strcat(buffer, NULL); // SIGABRT
strcat(NULL, ""); // Segmentation Fault

strncat

nってなんだよ畜生。strcatの改良版?らしい。ちょっぴりめんどくさい挙動する。

第3引数に第2引数に渡した文字列からどれだけの長さをコピーするかを指定する。

バッファオバーランに注意。

const int bufferSize = 20;
char buffer[bufferSize];
buffer[0] = '\0';
const char *text = "ABCDE";

strncat(buffer, text, 1);
puts((const char *)buffer); // A

strncat(buffer, text, 3);
puts((const char *)buffer); // AABC

strncat(buffer, text, strlen(text));
puts((const char *)buffer); // AABCABCDE (9文字)

// このコードはバッファオーバーランする
strncat(buffer, "01234567890123456789", bufferSize - strlen(buffer));

puts((const char *)buffer); // AABCABCDE01234567890 (20文字)
/*
 bufferSize 20なのに 20文字書き込んでしまった…終端文字列がはみ出た。
 */
char buffer[15];
memset(buffer, '#', sizeof(buffer));
buffer[3] = '\0';
buffer[sizeof(buffer) - 1] = '\0';
const char *text = "abcde\0ABCDE"; // 12文字

//
// さてこれはどこまで連結されるのか。
//
char *returnValue = strncat(buffer, text, 12);

puts((const char *)buffer); // "###abcde"

char *pointer = buffer;
while (*pointer++);

puts((const char *)pointer); // "#####";

puts(returnValue == buffer ? "YES" : "NO"); // "YES"

ということで指定された長さに達する前に文字終端が見えたら、文字列の連結はストップする。

char buffer[10];

strncat(buffer, NULL, 0); // SIGABRT
strncat(NULL, "", 0); // Segmentation Fault
strncat(buffer, "", -1); // SIGABRT

strcpy

こいつは 「memcpy の替わりに memmove 使おう」と同様の問題を孕む。

もちろんバッファオーバーランにも注意。

char buffer[10] = "123456789";
const char *text = "ABCDE";

strcpy(buffer, text);
puts((const char *)buffer); // "ABCDE"

char *pointer = buffer;
while (*pointer++);
puts((const char *)pointer); // "789"

strcpy(pointer, text); // Buffer Over Run
puts((const char *)pointer); // "ABCDE"
//
// 領域が重なる場合の動作は未定義なので
// こういうことするのはNG
//
char buffer[100] = "123456789";
strcpy(&buffer[2], buffer);
puts((const char *)buffer);
char buffer[10];
char *returnValue = strcpy(buffer, "HOGE");
puts(returnValue == buffer ? "YES" : "NO"); // YES
/*
 返り血は第1引き数に渡したポインタと同じ
 */
char buffer[10];

strcpy(buffer, NULL); // Segmentation Fault
strcpy(NULL, ""); // Segmentation Fault

strncpy

strcpy の改良版らしい。

なんかすごくメンドウくさい挙動するんですけどこやつ。

char buffer[10];
memset(buffer, '#', 10);
buffer[10 - 1] = '\0';

const char *text = "12345";
strncpy(buffer, text, strlen(text));

puts((const char *)buffer); // "12345####"

/*
文字終端は入れてくれない。
コピーする文字列長 <= 第3引数のサイズの場合は
memmoveと同じ挙動をするらしい。
*/
char buffer[100];
memset(buffer, 127, 99);
buffer[100 - 1] = '\0';

strncpy(buffer, "12345", 10);

puts((const char *)buffer); // "12345"

for (int i = 0; i < 11; i++) {
  // 文字終端の後ろまでnull文字で埋める。親切心が垣間見える。
  printf("%d,", buffer[i]); // 49,50,51,52,53,0,0,0,0,0,127,
}
char buffer[10];
char *returnValue = strncpy(buffer, "HOGE", 0);
puts(returnValue == buffer ? "YES" : "NO"); // YES
/*
 返り血は第1引き数に渡したポインタと同じ
 */
char buffer[10];

strncpy(buffer, NULL, 0); // これは通る…いいの?
strncpy(buffer, NULL, 1); // Segmentation Fault
strncpy(NULL, "", 0); // これは通る…問題ないの?
strncpy(NULL, "", 1); // SIGABRT
strncpy(NULL, NULL, 0); // これは通る…マジで?
strncpy(buffer, "", -1); // SIGABRT
/*
 一部、通ってしまうのだけど問題ないのか分からず超怖い…
*/
Share