文字列を指すポインタとchar型配列との違い

C言語では文字列をポインタを使って扱うこともあれば、charの配列として扱う場合もあります。

char *p = “Orange”;
char a[ ] = “Apple”;

上の例では、p は「Orange」という文字列を指すポインタであり、a[ ] は「Apple」を格納している配列です。OrangeもAppleもヌル文字「\0」が終端文字として付加されます。

文字列を扱う方法が違うので以下の差が出てきます。

まず、ポインタの「p」も配列の「a」も文字列の先頭を指し示していますが、「p++」はできても「a++」はできません。a は配列の名前であり配列の名前はその先頭の要素の位置と同義ですが、ポインタの加減算とは異なるものであるため a++ のような使い方は出来ず、コンパイルエラーになります。また a に別のポインタアドレスを代入することも出来ません。「a = p」は間違いです。「p = a」は出来ます。

また、「Apple」という文字列は配列で確保しているため書き換えることができますが「Orange」という文字列の書き換えはできません。「Orange」を書き換えた場合の動きは不定です。僕の環境(ubuntu)で、*pに値を代入(*p = ‘A’)をして実行してみたところコアダンプしてしまいました。

ちなみに、ポインタ「p」は「Orange」の文字列を指し示していますが、ポインタの向き先を「Apple」に変えることは可能です。

これらを確認したプログラムソースを載せておきます。

コピペするなら以下を使ってください。


#include <stdio.h>

int main( void )
{
	char	*p = "Orange";
	char	a[] = "Apple";
	char	*ap;

	printf( "p: %s\n", p );
	printf( "a: %s\n\n", a );

	/* Orangeを指し示すポンタの位置をずらす */
	p++;

	/* Appleを指し示すポンタの位置をずらす。注意: a++ はできません */
	ap = a;
	ap++;

	printf( "p++:  %s\n", p );
	printf( "ap++: %s\n\n", ap );

	/* Orangeの値は書き換えられません( *p に代入はできません)
	   Orangeを指すポインタの向き先を変えることはできます */
	p = a;
	printf( "p: %s\n", p );

	/* Appleの値は書き換えられます */
	*p = 'a';
	*ap = 'P';
	a[4] = 'E';
	printf( "a: %s\n", a );

	return 0;
}

実行結果は以下となります。

$ ./a.out
p: Orange
a: Apple

p++: range
ap++: pple

p: Apple
a: aPplE

C言語の関数ポインタの簡単なサンプルコード

C言語には関数ポインタという関数への場所を指し示すポインタがあります。この関数ポインタの書き方(使い方)を忘れてしまうので、サンプルコードを載せておきます。使い方はコードを見ながら説明してみます。

コピペするなら、以下を使ってください。


#include <stdio.h>

/* 関数宣言 */
int func( int a, int b );

int main( void )
{
	/* 関数ポインタ「funcptr」の宣言 */
	int (*funcptr)(int, int);

	/* 関数ポインタへの代入 */
	funcptr = func;

	/* 関数ポインタから関数の実行 */
	printf("関数func()の戻り値:%d\n", (*funcptr)( 3, 4 ) );

	return 0;
}

int func( int a, int b )
{
	return( a * b );
}

main関数から整数の「3」と「4」をfunc関数に渡して、func関数では「3×4」の結果をmain関数に返しています。実行結果は「関数func()の戻り値:12」と画面に表示されます。

まず最初に、関数へのポインタを格納する変数を「funcptr」として宣言しています。func関数は2つのint型の変数を引数に取り、戻り値がint型の関数です。この関数へのポインタ宣言は以下となります。

int (*funcptr)(int, int);

あくまでポインタ変数の宣言であるためfuncptrの前には「*(アスタリスク)」が必要です。またfuncptrをカッコで囲っていますが、このカッコも必要です。もしカッコがなかった場合、

int *funcptr(int, int);

となりますが、これはfuncptrが2つのintを引数にしてintのポインタを返す「関数」ということになってしまいますので。

次に、関数ポインタに関数へのアドレスを代入します。それが以下となります。

funcptr = func;

ここで注意すべきはfuncにカッコがないことです。つまり「funcptr = func();」としてはダメです。もしfuncにカッコがあるとfunc関数の呼び出しが行われてしまうからです。またfunc関数の前に「&演算子」がないことにも気をつけましょう。関数の末尾にカッコをつけない形で関数を参照する場合、コンパイラは「その関数のアドレスを参照する」となっているためです。

最後に、関数ポインタが指し示すアドレスの関数を実行します。コード上では以下の部分です。

printf(“関数func()の戻り値:%d\n”, (*funcptr)( 3, 4 ) );

「(*funcptr)( 3, 4 )」の戻り値をprintf関数で印字しています。funcptrの前の「*(アスタリスク)」はポインタが指し示すオブジェクト(この場合は関数)にアクセスしなければならないため必要です。またfuncptrを囲むカッコも必要です。

ちなみに別の書き方として、以下でもコンパイルは通ります(gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)の場合)。

printf(“関数func()の戻り値:%d\n”, funcptr( 3, 4 ) );

こちらのほうが直感的にはわかりやすいかもしれません。どちらを使うかは、好みの問題でしょうか。

– – – – – – – – – –
追記しました。「ポインタを返す関数」への関数ポインタを使ったサンプルも載せておきます。

コピペするなら、以下を使ってください。


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

/* 関数宣言 */
char *func( char *c );

/* 外部変数 */
char message[100] = "Hello world. ";

int main( void )
{
	/* 関数ポインタ「funcptr」の宣言 */
	char *(*funcptr)(char *);

	/* 関数ポインタへの代入 */
	funcptr = func;

	/* messageの出力 */
	printf("%s\n", message );

	/* 関数ポインタからmessageの書き換え */
	printf("%s\n", (*funcptr)("HELLO WORLD!") );

	return 0;
}

char *func( char *c )
{
	strcat( message, c );
	return message;
}

実行結果は以下となります。

$ ./a.out
Hello world.
Hello world. HELLO WORLD!

1つ目のサンプルコードとの違いは、関数ポインタの宣言の際に「*(アスタリスク)」がもう一つ付くことです(charのポインタを返す関数ということを示すため)。

char *(*funcptr)(char *);

関数ポインタへの代入や、関数ポインタからの関数の実行の仕方は同じになります。