C Language
文字列
サーチ…
前書き
Cでは、文字列は組み込み型ではありません。 C文字列は、 '\0'
によってヌル文字で終わる文字の1次元配列を持つ規則です。
これは、内容が"abc"
C文字列が4文字の'a'
、 'b'
、 'c'
、 '\0'
を持つことを意味します。
構文
- char str1 [] = "こんにちは、世界!"; / *変更可能* /
- char str2 [14] = "こんにちは、世界!"; / *変更可能* /
- char * str3 = "こんにちは、世界!"; / *変更不可* /
長さを計算する:strlen()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
/* Exit if no second argument is found. */
if (argc != 2)
{
puts("Argument missing.");
return EXIT_FAILURE;
}
size_t len = strlen(argv[1]);
printf("The length of the second argument is %zu.\n", len);
return EXIT_SUCCESS;
}
このプログラムは、2番目の入力引数の長さを計算し、結果をlen
格納します。次に、その長さを端末に出力します。たとえば、パラメータprogram_name "Hello, world!"
使用して実行すると、プログラムは出力しますThe length of the second argument is 13.
なぜなら、文字列Hello, world!
13文字です。
strlen
は、文字列の先頭から終了NUL文字'\0'
までのすべてのバイト数をカウントします 。したがって、文字列がNULで終了することが保証されている場合にのみ使用できます。
また、文字列にUnicode文字が含まれている場合、 strlen
は文字列内にいくつの文字があるかを教えません(いくつかの文字は複数バイトの長さかもしれないので)。このような場合には、文字( すなわち 、コード単位)を自分でカウントする必要があります。次の例の出力を考えてみましょう。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char asciiString[50] = "Hello world!";
char utf8String[50] = "Γειά σου Κόσμε!"; /* "Hello World!" in Greek */
printf("asciiString has %zu bytes in the array\n", sizeof(asciiString));
printf("utf8String has %zu bytes in the array\n", sizeof(utf8String));
printf("\"%s\" is %zu bytes\n", asciiString, strlen(asciiString));
printf("\"%s\" is %zu bytes\n", utf8String, strlen(utf8String));
}
出力:
asciiString has 50 bytes in the array
utf8String has 50 bytes in the array
"Hello world!" is 12 bytes
"Γειά σου Κόσμε!" is 27 bytes
コピーと連結:strcpy()、strcat()
#include <stdio.h>
#include <string.h>
int main(void)
{
/* Always ensure that your string is large enough to contain the characters
* and a terminating NUL character ('\0')!
*/
char mystring[10];
/* Copy "foo" into `mystring`, until a NUL character is encountered. */
strcpy(mystring, "foo");
printf("%s\n", mystring);
/* At this point, we used 4 chars of `mystring`, the 3 characters of "foo",
* and the NUL terminating byte.
*/
/* Append "bar" to `mystring`. */
strcat(mystring, "bar");
printf("%s\n", mystring);
/* We now use 7 characters of `mystring`: "foo" requires 3, "bar" requires 3
* and there is a terminating NUL character ('\0') at the end.
*/
/* Copy "bar" into `mystring`, overwriting the former contents. */
strcpy(mystring, "bar");
printf("%s\n", mystring);
return 0;
}
出力:
foo
foobar
bar
既存の文字列に追加または既存の文字列からコピーする場合は、NULで終了することを確認してください。
文字列リテラル( "foo"
)は、コンパイラによってNUL終了されます。
比較:strcmp()、strncmp()、strcasecmp()、strncasecmp()
strcase*
関数は、標準Cではなく、POSIX拡張です。
strcmp
関数は、2つのNULL終端文字配列を辞書的に比較します。関数は、最初の引数が辞書順で2番目の前に現れる場合は負の値を返し、等しい場合は0を、辞書順の順で最初の引数が現れた場合は正を返します。
#include <stdio.h>
#include <string.h>
void compare(char const *lhs, char const *rhs)
{
int result = strcmp(lhs, rhs); // compute comparison once
if (result < 0) {
printf("%s comes before %s\n", lhs, rhs);
} else if (result == 0) {
printf("%s equals %s\n", lhs, rhs);
} else { // last case: result > 0
printf("%s comes after %s\n", lhs, rhs);
}
}
int main(void)
{
compare("BBB", "BBB");
compare("BBB", "CCCCC");
compare("BBB", "AAAAAA");
return 0;
}
出力:
BBB equals BBB
BBB comes before CCCCC
BBB comes after AAAAAA
strcmp
として、 strcasecmp
関数は、各文字を小文字の対応に変換した後で、辞書的にその引数を比較します。
#include <stdio.h>
#include <string.h>
void compare(char const *lhs, char const *rhs)
{
int result = strcasecmp(lhs, rhs); // compute case-insensitive comparison once
if (result < 0) {
printf("%s comes before %s\n", lhs, rhs);
} else if (result == 0) {
printf("%s equals %s\n", lhs, rhs);
} else { // last case: result > 0
printf("%s comes after %s\n", lhs, rhs);
}
}
int main(void)
{
compare("BBB", "bBB");
compare("BBB", "ccCCC");
compare("BBB", "aaaaaa");
return 0;
}
出力:
BBB equals bBB
BBB comes before ccCCC
BBB comes after aaaaaa
strncmp
とstrncasecmp
は最大でn文字を比較します:
#include <stdio.h>
#include <string.h>
void compare(char const *lhs, char const *rhs, int n)
{
int result = strncmp(lhs, rhs, n); // compute comparison once
if (result < 0) {
printf("%s comes before %s\n", lhs, rhs);
} else if (result == 0) {
printf("%s equals %s\n", lhs, rhs);
} else { // last case: result > 0
printf("%s comes after %s\n", lhs, rhs);
}
}
int main(void)
{
compare("BBB", "Bb", 1);
compare("BBB", "Bb", 2);
compare("BBB", "Bb", 3);
return 0;
}
出力:
BBB equals Bb
BBB comes before Bb
BBB comes before Bb
トークン化:strtok()、strtok_r()、strtok_s()
関数strtok
は、区切り文字のセットを使用して、文字列を小さな文字列またはトークンに分割します。
#include <stdio.h>
#include <string.h>
int main(void)
{
int toknum = 0;
char src[] = "Hello,, world!";
const char delimiters[] = ", !";
char *token = strtok(src, delimiters);
while (token != NULL)
{
printf("%d: [%s]\n", ++toknum, token);
token = strtok(NULL, delimiters);
}
/* source is now "Hello\0, world\0\0" */
}
出力:
1: [Hello]
2: [world]
デリミタの文字列には1つ以上のデリミタが含まれ、 strtok
呼び出すたびに異なるデリミタ文字列を使用できます。
同じソース文字列をトークン化し続けるstrtok
呼び出しは、元の文字列をもう一度渡すべきではなく、その代わりに最初の引数としてNULL
を渡しNULL
。同じソース文字列が渡された場合、代わりに最初のトークンが再トークンされます。つまり、同じ区切り文字を指定すると、 strtok
は単に最初のトークンを戻します。
strtok
はトークンに新しいメモリを割り当てないので、ソース文字列を変更することに注意してください。つまり、上の例では、文字列src
は、 strtok
への呼び出しによって返されたポインタによって参照されるトークンを生成するように操作されます。つまり、ソース文字列をconst
することはできません(文字列リテラルにすることはできません)。また、区切りバイトのアイデンティティが失われていることも意味します(例では、 "、"と "!"がソース文字列から効果的に削除され、どの区切り文字が一致したかはわかりません)。
また、ソース文字列内の複数の区切り文字は1つとして扱われることに注意してください。この例では、2番目のコンマは無視されます。
strtok
は、解析中に静的バッファを使用するため、スレッドセーフでもリエントラントでもありません。これは、関数呼び出した場合ということstrtok
、使用している間に呼び出していない機能strtok
使用することもできstrtok
、それが使用して自分自身をある任意の関数によって呼び出すことはできませんstrtok
。
strtok
が再入可能ではないという事実によって引き起こされる問題を示す例は次のとおりです。
char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ",");
do
{
char *part;
/* Nested calls to strtok do not work as desired */
printf("[%s]\n", first);
part = strtok(first, ".");
while (part != NULL)
{
printf(" [%s]\n", part);
part = strtok(NULL, ".");
}
} while ((first = strtok(NULL, ",")) != NULL);
出力:
[1.2]
[1]
[2]
予想される動作は、外側の点であるdo while
ループ(各進数文字列からなる3つのトークンを作成する必要が"1.2"
、 "3.5"
、 "4.2"
これらの各々のために、) strtok
内部ループが別に分割しなければならないために呼び出し( "1"
、 "2"
、 "3"
、 "5"
、 "4"
、 "2"
)を含む。
しかし、 strtok
はリエントラントではないので、これは発生しません。代わりに、最初のstrtok
正しく "1.2 \ 0"トークンを作成し、内部ループはトークン"1"
と"2"
正しく作成します。しかし、外側ループ内のstrtok
は、内側ループによって使用される文字列の最後にあり、すぐにNULLを返します。 src
配列の2番目と3番目の部分文字列はまったく解析されません。
標準のCライブラリには、スレッドセーフなバージョンやリエントラントなバージョンは含まれていませんが、POSIXのstrtok_r
などの他のバージョンもあります。 MSVCではstrtok
と同等のstrtok_s
がスレッドセーフであることに注意してください。
C11には、 strtok_s
という名前のスレッドセーフでリエントラントなバージョンを提供するオプションパートAnnex Kがあります。 __STDC_LIB_EXT1__
を使用して機能をテストできます。このオプション部分は広くサポートされていません。
strtok_s
関数はPOSIX strtok_s
関数とは異なり、トークン化されている文字列の外部への格納や実行時制約のチェックをstrtok_r
ます。しかし、正しく書かれたプログラムでは、 strtok_s
とstrtok_r
は同じように動作します。
この例でstrtok_s
を使用すると、正しい応答が得られるようになります。
/* you have to announce that you want to use Annex K */
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif
char src[] = "1.2,3.5,4.2";
char *next = NULL;
char *first = strtok_s(src, ",", &next);
do
{
char *part;
char *posn;
printf("[%s]\n", first);
part = strtok_s(first, ".", &posn);
while (part != NULL)
{
printf(" [%s]\n", part);
part = strtok_s(NULL, ".", &posn);
}
}
while ((first = strtok_s(NULL, ",", &next)) != NULL);
出力は次のようになります。
[1.2]
[1]
[2]
[3.5]
[3]
[5]
[4.2]
[4]
[2]
特定の文字の最初か最後のオカレンスを探します:strchr()、strrchr()
strchr
関数とstrrchr
関数は、NULで終了する文字配列内の文字列内の文字を検索します。 strchr
は最初のオカレンスへのポインタを返し、 strrchr
は最後のオカレンスへのポインタを返します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char toSearchFor = 'A';
/* Exit if no second argument is found. */
if (argc != 2)
{
printf("Argument missing.\n");
return EXIT_FAILURE;
}
{
char *firstOcc = strchr(argv[1], toSearchFor);
if (firstOcc != NULL)
{
printf("First position of %c in %s is %td.\n",
toSearchFor, argv[1], firstOcc-argv[1]); /* A pointer difference's result
is a signed integer and uses the length modifier 't'. */
}
else
{
printf("%c is not in %s.\n", toSearchFor, argv[1]);
}
}
{
char *lastOcc = strrchr(argv[1], toSearchFor);
if (lastOcc != NULL)
{
printf("Last position of %c in %s is %td.\n",
toSearchFor, argv[1], lastOcc-argv[1]);
}
}
return EXIT_SUCCESS;
}
出力( pos
という名前の実行ファイルを生成した後):
$ ./pos AAAAAAA
First position of A in AAAAAAA is 0.
Last position of A in AAAAAAA is 6.
$ ./pos BAbbbbbAccccAAAAzzz
First position of A in BAbbbbbAccccAAAAzzz is 1.
Last position of A in BAbbbbbAccccAAAAzzz is 15.
$ ./pos qwerty
A is not in qwerty.
strrchr
一般的な使い方の1つは、パスからファイル名を抽出することです。たとえば、 C:\Users\eak\myfile.txt
からmyfile.txt
を抽出するには:
char *getFileName(const char *path)
{
char *pend;
if ((pend = strrchr(path, '\')) != NULL)
return pend + 1;
return NULL;
}
文字列の文字の繰り返し処理
文字列の長さがわかっている場合は、forループを使用してその文字を繰り返し処理できます。
char * string = "hello world"; /* This 11 chars long, excluding the 0-terminator. */
size_t i = 0;
for (; i < 11; i++) {
printf("%c\n", string[i]); /* Print each character of the string. */
}
代わりに、標準の関数strlen()
を使用して、文字列の内容がわからない場合に文字列の長さを取得することができます。
size_t length = strlen(string);
size_t i = 0;
for (; i < length; i++) {
printf("%c\n", string[i]); /* Print each character of the string. */
}
最後に、Cの文字列がヌルで終了することが保証されていることを利用することができます(前の例のstrlen()
に渡したときにすでに行っていました;-))。そのサイズに関係なく配列全体を繰り返し処理し、ヌル文字に達すると反復処理を停止できます:
size_t i = 0;
while (string[i] != '\0') { /* Stop looping when we reach the null-character. */
printf("%c\n", string[i]); /* Print each character of the string. */
i++;
}
文字列の基本的な紹介
Cでは、 文字列はヌル文字( '\ 0')で終わる文字列です。
文字列リテラルを使用して文字列を作成することができます。文字列リテラルは二重引用符で囲まれた文字列です。例えば、文字列リテラル"hello world"
取る。文字列リテラルは自動的にヌルで終了します。
いくつかの方法を使って文字列を作成することができます。例えば、 char *
を宣言して文字列の最初の文字を指すように初期化することができます:
char * string = "hello world";
上記のようにchar *
を文字列定数に初期化する場合、通常、文字列自体は読み取り専用データに割り当てられます。 string
は配列の最初の要素へのポインタで、文字'h'
です。
文字列リテラルは読み取り専用メモリに割り当てられているため、変更不可能です1 。それを変更しようとすると未定義の動作につながるので、 const
を追加してこのようなコンパイル時エラーを取得する方が良い
char const * string = "hello world";
これは、同様の効果2を持っています
char const string_arr[] = "hello world";
変更可能な文字列を作成するには、文字配列を宣言し、文字列リテラルを使用してその内容を初期化することができます。
char modifiable_string[] = "hello world";
これは以下と同じです:
char modifiable_string[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};
2番目のバージョンは中括弧で囲まれたイニシャライザを使用しているため、通常は文字配列に'\0'
文字が明示的に含まれていない限り、文字列は自動的にNULLで終了しません。
1変更不可能とは、文字列リテラルの文字を変更できないことを意味しますが、ポインタstring
を変更することができます(別の場所を指すか、増分または減分することができます)。
2両方の文字列は、両方の文字列の文字を変更できないという意味で同様の効果があります。 string
はchar
へのポインターであり、 変更可能なl値であるため、配列string_arr
が変更不可能なl値である間にインクリメントしたり、他の場所をポイントしたりすることはできません。
ストリングの配列の作成
文字列の配列は、いくつかのことを意味します:
- 要素が
char *
sの配列 - 要素が
char
の配列である配列
次のように文字ポインタの配列を作成できます:
char * string_array[] = {
"foo",
"bar",
"baz"
};
注意:文字列リテラルをchar *
代入すると、文字列自体が読み込み専用メモリに割り当てられます。ただし、配列string_array
は読み取り/書き込みメモリに割り当てられます。つまり、配列内のポインタを変更できますが、ポインタが指す文字列を変更することはできません。
Cでは、main argv
(プログラムの実行時に渡されたコマンドライン引数の配列)へのパラメータは、 char *
: char * argv[]
配列です。
文字配列の配列を作成することもできます。文字列は文字の配列なので、文字列の配列は単なる要素の文字配列である配列です。
char modifiable_string_array_literals[][4] = {
"foo",
"bar",
"baz"
};
これは次のようになります。
char modifiable_string_array[][4] = {
{'f', 'o', 'o', '\0'},
{'b', 'a', 'r', '\0'},
{'b', 'a', 'z', '\0'}
};
配列の2番目の次元のサイズとして4
を指定することに注意してください。私たちの配列の各文字列は実際には4バイトです。ヌル終端文字を含める必要があるからです。
strstr
/* finds the next instance of needle in haystack
zbpos: the zero-based position to begin searching from
haystack: the string to search in
needle: the string that must be found
returns the next match of `needle` in `haystack`, or -1 if not found
*/
int findnext(int zbpos, const char *haystack, const char *needle)
{
char *p;
if (((p = strstr(haystack + zbpos, needle)) != NULL)
return p - haystack;
return -1;
}
strstr
はhaystack
(最初の)引数でneedle
指す文字列を検索します。見つかった場合、 strstr
はオカレンスのアドレスを返します。 needle
が見つからない場合は、NULLを返します。 zbpos
を使って同じ針を何度も何度も探し続けることはありません。最初のインスタンスをスキップするために、 zbpos
オフセットを追加します。メモ帳のクローンは、次のような検索ダイアログを実装するためにfindnext
をこのように呼び出すことができます。
/*
Called when the user clicks "Find Next"
doc: The text of the document to search
findwhat: The string to find
*/
void onfindnext(const char *doc, const char *findwhat)
{
static int i;
if ((i = findnext(i, doc, findwhat)) != -1)
/* select the text starting from i and ending at i + strlen(findwhat) */
else
/* display a message box saying "end of search" */
}
文字列リテラル
文字列リテラルは、ヌル終了した静的持続時間のchar
配列を表します。それらは静的な記憶期間を持つため、文字列リテラルまたは同じ基礎となる配列へのポインタは、自動配列へのポインタができないいくつかの方法で安全に使用できます。たとえば、関数から文字列リテラルを返すと、明確に定義された動作が得られます。
const char *get_hello() {
return "Hello, World!"; /* safe */
}
歴史的な理由から、文字列リテラルに対応する配列の要素は正式にはconst
はありません。それにもかかわらず、それらを変更しようとすると未定義の動作が発生します。通常、文字列リテラルに対応する配列を変更しようとするプログラムは、クラッシュするか、そうでなければ誤動作します。
char *foo = "hello";
foo[0] = 'y'; /* Undefined behavior - BAD! */
ポインターが文字列リテラルを指している場合、またはポインターの参照先const
を宣言して、このような未定義の振る舞いを誤って行うことを避けることが推奨されます。
const char *foo = "hello";
/* GOOD: can't modify the string pointed to by foo */
一方、文字列リテラルの基本となる配列へのポインタは、本質的に特別なものではありません。その値は他のものを指すように自由に変更することができます:
char *foo = "hello";
foo = "World!"; /* OK - we're just changing what foo points to */
さらに、 char
配列のイニシャライザはchar
列リテラルと同じ形式を持つことができますが、このようなイニシャライザの使用は、初期化された配列の文字列リテラルの特性を与えません。イニシャライザは、単に配列の長さと初期値を指定するだけです。特に、明示的に宣言されていなければ要素は変更可能ですconst
:
char foo[] = "hello";
foo[0] = 'y'; /* OK! */
文字列をゼロにする
文字列(または他のメモリブロック)をゼロにするためにmemset
を呼び出すことができます。
str
はゼロにする文字列、 n
は文字列中のバイト数です。
#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[42] = "fortytwo";
size_t n = sizeof str; /* Take the size not the length. */
printf("'%s'\n", str);
memset(str, '\0', n);
printf("'%s'\n", str);
return EXIT_SUCCESS;
}
印刷物:
'fortytwo'
''
もう一つの例:
#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>
#define FORTY_STR "forty"
#define TWO_STR "two"
int main(void)
{
char str[42] = FORTY_STR TWO_STR;
size_t n = sizeof str; /* Take the size not the length. */
char * point_to_two = strstr(str, TWO_STR);
printf("'%s'\n", str);
memset(point_to_two, '\0', n);
printf("'%s'\n", str);
memset(str, '\0', n);
printf("'%s'\n", str);
return EXIT_SUCCESS;
}
印刷物:
'fortytwo'
'forty'
''
strspnとstrcspn
文字列が与えられた場合、 strspn
は最初の部分文字列(span)の長さを特定の文字リストのみから計算します。 strcspn
は似ていますが、リストされた文字以外の文字からなる最初の部分文字列の長さを計算する点を除いては同じです:
/*
Provided a string of "tokens" delimited by "separators", print the tokens along
with the token separators that get skipped.
*/
#include <stdio.h>
#include <string.h>
int main(void)
{
const char sepchars[] = ",.;!?";
char foo[] = ";ball call,.fall gall hall!?.,";
char *s;
int n;
for (s = foo; *s != 0; /*empty*/) {
/* Get the number of token separator characters. */
n = (int)strspn(s, sepchars);
if (n > 0)
printf("skipping separators: << %.*s >> (length=%d)\n", n, s, n);
/* Actually skip the separators now. */
s += n;
/* Get the number of token (non-separator) characters. */
n = (int)strcspn(s, sepchars);
if (n > 0)
printf("token found: << %.*s >> (length=%d)\n", n, s, n);
/* Skip the token now. */
s += n;
}
printf("== token list exhausted ==\n");
return 0;
}
ワイド文字列を使用する類似関数はwcsspn
とwcscspn
です。彼らは同じように使用されています。
文字列のコピー
ポインタの割り当ては文字列をコピーしません
あなたは使用することができます=
整数をコピーする作業をしますが、使用することはできません=
そう使用して、終端のヌル文字と文字の配列として表現されているCにCで文字列内の文字列をコピーする演算子を=
(アドレスのみが保存されます演算子をポインタ)。
#include <stdio.h>
int main(void) {
int a = 10, b;
char c[] = "abc", *d;
b = a; /* Integer is copied */
a = 20; /* Modifying a leaves b unchanged - b is a 'deep copy' of a */
printf("%d %d\n", a, b); /* "20 10" will be printed */
d = c;
/* Only copies the address of the string -
there is still only one string stored in memory */
c[1] = 'x';
/* Modifies the original string - d[1] = 'x' will do exactly the same thing */
printf("%s %s\n", c, d); /* "axc axc" will be printed */
return 0;
}
上記の例は、 char d[3]
ではなくchar *d
を使用しているためコンパイルされています。後者を使用するとコンパイルエラーが発生します。 Cの配列に割り当てることはできません。
#include <stdio.h>
int main(void) {
char a[] = "abc";
char b[8];
b = a; /* compile error */
printf("%s\n", b);
return 0;
}
標準関数を使用した文字列のコピー
strcpy()
実際に文字列をコピーするには、 strcpy()
関数をstring.h
で使用できます。コピーする前に、十分なスペースを宛先に割り当てる必要があります。
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "abc";
char b[8];
strcpy(b, a); /* think "b special equals a" */
printf("%s\n", b); /* "abc" will be printed */
return 0;
}
snprintf()
バッファオーバーランを避けるために、 snprintf()
を使用することができます。テンプレート文字列を解析する必要があるため、パフォーマンス上の理由で最適なソリューションではありませんが、標準ライブラリですぐに使用できる文字列をコピーするための唯一のバッファ制限セーフ機能です。
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "012345678901234567890";
char b[8];
#if 0
strcpy(b, a); /* causes buffer overrun (undefined behavior), so do not execute this here! */
#endif
snprintf(b, sizeof(b), "%s", a); /* does not cause buffer overrun */
printf("%s\n", b); /* "0123456" will be printed */
return 0;
}
strncat()
strncat()
( strcat()
バッファオーバーフローチェックバージョンstrncat()
を使用することです - それはコピーする最大バイト数を指示する3番目の引数をとります:
char dest[32];
dest[0] = '\0';
strncat(dest, source, sizeof(dest) - 1);
/* copies up to the first (sizeof(dest) - 1) elements of source into dest,
then puts a \0 on the end of dest */
この定式化はsizeof(dest) - 1
使用することに注意してください。 strncat()
常にstrncat()
を追加しますが、それは文字列のサイズでカウントされません(混乱とバッファの上書きの原因strncat()
ので、これは重要です。
また、空でない文字列の後の代替 - 連結はさらに厄介であることに注意してください。検討してください:
char dst[24] = "Clownfish: ";
char src[] = "Marvin and Nemo";
size_t len = strlen(dst);
strncat(dst, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);
出力は次のとおりです。
23: [Clownfish: Marvin and N]
ただし、長さとして指定されたサイズは、宛先の配列のサイズではなく 、その中に残っているスペースの量であり、終端のヌルバイトを数えないことに注意してください。これは大きな上書きの問題を引き起こす可能性があります。それはまた、少し無駄です。 length引数を正しく指定するには、宛先のデータの長さを知っているので、 strncat()
再スキャンを省略して、既存のコンテンツの末尾にnullバイトのアドレスを指定することができます。
strcpy(dst, "Clownfish: ");
assert(len < sizeof(dst) - 1);
strncat(dst + len, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);
これは以前と同じ出力を生成しますが、 strncat()
はコピーを開始する前にdst
の既存の内容をスキャンする必要はありません。
strncpy()
最後のオプションはstrncpy()
関数です。あなたが最初に来るはずだと思うかもしれませんが、それは2つの主な問題点を持っている、
-
strncpy()
を介したコピーがバッファ制限にstrncpy()
と、終了ヌル文字は書き込まれません。 -
strncpy()
は、必要に応じて常にデスティネーションを完全に満たします。
(このような奇妙な実装は歴史的なもので、最初はUNIXのファイル名を扱うためのものでした )
それを使用する唯一の正しい方法は、手動でヌル終了を保証することです。
strncpy(b, a, sizeof(b)); /* the third parameter is destination buffer size */
b[sizeof(b)/sizeof(*b) - 1] = '\0'; /* terminate the string */
printf("%s\n", b); /* "0123456" will be printed */
それでも大きなバッファがある場合、追加のヌルパディングのためにstrncpy()
を使用することは非常に非効率的になります。
文字列を数値に変換する:atoi()、atof()(危険な、使用しないでください)
警告:関数atoi
、 atol
、 atoll
、 atof
は本質的に安全ではありません : 結果の値が表現できない場合、その動作は未定義です。 (7.20.1p1)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int val;
if (argc < 2)
{
printf("Usage: %s <integer>\n", argv[0]);
return 0;
}
val = atoi(argv[1]);
printf("String value = %s, Int value = %d\n", argv[1], val);
return 0;
}
変換される文字列が範囲内の有効な10進整数の場合、関数は次のように動作します。
$ ./atoi 100
String value = 100, Int value = 100
$ ./atoi 200
String value = 200, Int value = 200
数字で始まり、何かが続く文字列の場合、最初の数字だけが解析されます。
$ ./atoi 0x200
0
$ ./atoi 0123x300
123
他のすべての場合、その動作は未定義です。
$ ./atoi hello
Formatting the hard disk...
上記のあいまいさとこの未定義の動作のため、 atoi
ファミリの関数は決して使用しないでください。
-
long int
に変換するには、atol()
代わりにstrtol()
使用します。 -
double
に変換するには、strtod()
代わりにatof()
ます。
-
long long int
変換するには、atoll()
代わりにstrtoll()
使用します。
文字列形式のデータの読み書き
フォーマットされたデータを文字列に書き出す
int sprintf ( char * str, const char * format, ... );
sprintf
関数を使用してfloatデータを文字列に書き出します。
#include <stdio.h>
int main ()
{
char buffer [50];
double PI = 3.1415926;
sprintf (buffer, "PI = %.7f", PI);
printf ("%s\n",buffer);
return 0;
}
文字列から書式設定されたデータを読み込む
int sscanf ( const char * s, const char * format, ...);
sscanf
関数を使用して、書式設定されたデータを解析します。
#include <stdio.h>
int main ()
{
char sentence []="date : 06-06-2012";
char str [50];
int year;
int month;
int day;
sscanf (sentence,"%s : %2d-%2d-%4d", str, &day, &month, &year);
printf ("%s -> %02d-%02d-%4d\n",str, day, month, year);
return 0;
}
安全にストリングを数値に変換する:strtoX関数
C99以降、Cライブラリには、文字列を数値として解釈する一連の安全変換関数があります。それらの名前はstrtoX
の形式であり、 X
はl
、 ul
、 d
などのいずれかであり、変換のターゲット・タイプを判別します
double strtod(char const* p, char** endptr);
long double strtold(char const* p, char** endptr);
それらは、コンバージョンにオーバーフローまたはアンダーフローがあることを確認します。
double ret = strtod(argv[1], 0); /* attempt conversion */
/* check the conversion result. */
if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE)
return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE)
return; /* numeric underflow in in string */
/* At this point we know that everything went fine so ret may be used */
実際に文字列に数字がまったく含まれていない場合、 strtod
この使用法は0.0
返します。
これが満足のいくものでなければ、追加のパラメータendptr
を使用できます。文字列中の検出された番号の終わりを指すポインタへのポインタです。上記のように0
、またはNULL
に設定されている場合は、単に無視されます。
このendptr
パラメータは、変換が成功したかどうかを示し、成功した場合は終了した番号を示しendptr
。
char *check = 0;
double ret = strtod(argv[1], &check); /* attempt conversion */
/* check the conversion result. */
if (argv[1] == check)
return; /* No number was detected in string */
else if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE)
return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE)
return; /* numeric underflow in in string */
/* At this point we know that everything went fine so ret may be used */
より広い整数型に変換する類似の関数があります:
long strtol(char const* p, char** endptr, int nbase);
long long strtoll(char const* p, char** endptr, int nbase);
unsigned long strtoul(char const* p, char** endptr, int nbase);
unsigned long long strtoull(char const* p, char** endptr, int nbase);
これらの関数には、数値が書き込まれる数値ベースを保持する第3のパラメータnbase
があります。
long a = strtol("101", 0, 2 ); /* a = 5L */
long b = strtol("101", 0, 8 ); /* b = 65L */
long c = strtol("101", 0, 10); /* c = 101L */
long d = strtol("101", 0, 16); /* d = 257L */
long e = strtol("101", 0, 0 ); /* e = 101L */
long f = strtol("0101", 0, 0 ); /* f = 65L */
long g = strtol("0x101", 0, 0 ); /* g = 257L */
nbase
の特別な値0
は、文字列がCプログラムで数値リテラルが解釈されるのと同じ方法で解釈されることを意味します。接頭辞0x
は16進表現に対応し、それ以外の場合は先頭の0
は8進数であり、他のすべての数字は10進数で表されます。
したがって、コマンドラインの引数を数値として解釈する最も実用的な方法は、次のようになります。
int main(int argc, char* argv[] {
if (argc < 1)
return EXIT_FAILURE; /* No number given. */
/* use strtoull because size_t may be wide */
size_t mySize = strtoull(argv[1], 0, 0);
/* then check conversion results. */
...
return EXIT_SUCCESS;
}
つまり、プログラムは8進数、10進数、または16進数のパラメータで呼び出すことができます。