LoginSignup
0
2

C言語!!文字列char型の配列を作りたい!!線形探索!ポインタのポインタ!

Last updated at Posted at 2024-01-13

初めに 謝辞

出典元柴田望洋先生の著書『新・明解C言語で学ぶアルゴリズムとデータ構造』では、数値型の線形探索を行っており、これを文字列にしようという試みです。

元のソースコードでは、int* x;にて定義された数値型配列をsearch関数の仮引数の中でconst int x[]として受け取っており、「読み取り専用」と解釈しました。
そこで、本題の目論見に際しても、同様の仕様を心掛けんとし、仕様を「読み取り専用」に寄せようと致しました。が、現時点で叶いませんでした。

@fujitanozomu 様、@SaitoAtsuhi 様には本記事投稿後早速コメント(※コメントのスレッドへ移動※)を頂き、数々のご指摘とご指南を頂きまして、ありがとうございます。
予めご指導に謝意を表します。
(参照:gcc,clang,MSVCでのソースコードのAnalysis一覧)
https://godbolt.org/z/1r7YW78Y6

🎉結論🎉

StackOverFlowにて、『本題』以下に続く本記事での取り組み及び、コメント(※コメントのスレッドへ移動※)でのやり取りに際して最終的に行き詰まりましたので、質問を行いました。
※↓↓↓大変ボリュームのあるかなり充実したQ&A記事となりました!!↓↓↓※
How to pass a char** to a function that expects a const array of const char pointers
ーStackOverflowー

その結果、回答を頂き、下記のように書くことで、どのCompiler(gcc,clang,msvc)においてもWarningを出すことなく、本来の目的を叶えられることが分かりました!!
結果的に目的とする実装を実現できたソースコードのAnalysis(C言語コンパイラー:gcc,clang,MSVC)は次の通りです。⇒https://godbolt.org/z/x3s51x5Td

ソースコードは以下のようになります!!
実引数のキャストと仮引数のキャストが本稿の肝心なポイントです!!

ソースコード

// Linear Search

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

int scanf_s(const char *format ,...); // for gcc and clang environment
// int search(const char **a, int n, char *key) {       //※[1]not allowed in c lang specification⇒NG
// int search(char * const *a, int n, char *key) {      //※[2]⇒different from what was intended

// int search(const char * const *a, int n, char *key) {//※[3]⇒⭕OK, as a result...
                                                        // or I should say it's better!!😊✨✨✨
// int search(char const * const *a, int n, char *key) {//※[4]Same as above...
                                                        //    ⇒⭕OK, as a result...
                                                        // or I should say it's better!!😊✨✨✨
// int search(const char * const a[], int n, char *key) {//※[5]⇒different from what was intended

// int search(const char *a[], int n, char *key) {      //※[6]I thought this was ok, but warning occured in gcc and clang!!
int search(const char* const a[], int n, char *key) {   //🚨in conclusion, gcc,clang and MSVC only allowing this!!
    int i = 0;

    for (i = 0; i < n; i++) {
        if (strcmp(a[i], key) == 0)
            return i;
    }
    return -1;

    /* or while style!! the reference book shows this style!!
    while (1) {
        if (i == n)
            return -1;
        if (strcmp(a[i], key) == 0)
            return i;
        i++;
    }
    */
}

int main(void) {
    char **x;
    char *ky;
    int nx;
    int idx;
    int i;

    puts("Linear Search");
    printf("How many Elements??:");
    scanf_s("%d", &nx);
    
    x = malloc(sizeof(char*) * nx);
    if (x == NULL) {
        printf("Pointer to Pointer x malloc failed!!\n");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < nx; i++) {
        printf("x[%d]:", i);
        x[i] = malloc(sizeof(char) * 35);
        if (x[i] == NULL) {
            printf("Pointer Array x[%d] malloc failed!!\n", i);
            exit(EXIT_FAILURE);
        }
        scanf_s("%s", x[i], 35);
    }

    printf("Target Value:");
    ky = malloc(sizeof(char) * 35);
    if (x == NULL) {
        printf("target value malloc failed!!\n");
        exit(EXIT_FAILURE);
    }
    // Or
    // ky = calloc(35, sizeof(char));
    scanf_s("%s", ky, 35);

    idx = search((const char* const *)x, nx, ky);   // 🚨ここのキャストが肝心

    if (idx == -1)
        puts("no target value.");
    else
        printf("%s is in x[%d]!!\n", ky, idx);

    free(ky);
    for (i = 0; i < nx; i++) {
        free(x[i]);
    }
    free(x);
    system("pause");
    return 0;
}

本題

柴田望洋先生の著書『新・明解C言語で学ぶアルゴリズムとデータ構造』を実践していて、
探索の章で線形探索List3_1をやっていた時に思ったこと・・・
「int型の配列で数値を探索をするのではなくて、char型の文字列の探索だとしたら、どうするんだろう・・・?」

独力で解決しようとして、本当に混乱を来しました。
BingChat、もとい、Copilot(GPT4)を活用しました。(が、最終的には、これにより直接的に、解に至った訳ではありません。本記事コメント(※コメントのスレッドへ移動※)で頂けましたご指導に沿います。)

結果的には、ポインタのポインタでchar型配列を宣言して作成すると良いのですが、一旦各種Compiler(gcc,clang,MSVC)でのAnalysis(でのwarning有無)に沿った自分調べの試行錯誤の結果において、次のようにすると良いという形に収まっています!!
(※🎉結論🎉にて、ソースコードは更新されました※)

// 線形探索

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

/* 仮引数でchar **xを読み取り専用にしようとして足掻いた軌跡 */
// int search(const char **a, int n, char *key) {		//※言語仕様上許されない。⇒NG
// int search(char* const *a, int n, char *key) {		//※ポインタが指すポインタのアドレスがconstになるので違う⇒NG
// int search(const char* const *a, int n, char *key) {	//※char **をconst char* const *型の仮引数で受けることは許されているらしいが、gcc,clangにてwarningが出ることが判明!!
// int search(char const * const *a, int n, char *key) {//※上と同様。同じ意味になる。
// int search(const char* const a[], int n, char* key) {//※上と同様。同じ意味になる。
// int search(const char* a[], int n, char* key) {		//※これで行けるかと思ったが間違い。gcc,clangにてwarningが出る。
int search(char** a, int n, char* key) {			    //結論、gcc,clang,MSVCでwarningが出ないのは唯一これのみ・・・
	int i = 0;

	/* 頂いたコメントを反映 「while文よりforを用いる」*/
	for (i = 0; i < n; i++) {
		if (strcmp(a[i], key) == 0)
			return i;
	}
	return -1;

	/* 或いは、while文で次のようにする。
	 *『新・明解C言語で学ぶアルゴリズムとデータ構造』はこっち!!
	while (1) {
		if (i == n)
			return -1;
		if (strcmp(a[i], key) == 0)
			return i;
		i++;
	}
	*/
}

int main(void) {
	char** x;
	char* ky;
	int nx;
	int idx;
	int i;

	puts("線形探索");
	printf("要素数:");
	scanf_s("%d", &nx);
	
	x = malloc(sizeof(char*) * nx);
	/* 頂いたコメントを反映して追加 「malloc不正の場合の処理」*/
	if (x == NULL) {
		printf("char型配列ポインタのポインタのxのメモリの動的確保に失敗しました。\n");
		exit(EXIT_FAILURE);
	}

	for (i = 0; i < nx; i++) {
		printf("x[%d]:", i);
		x[i] = malloc(sizeof(char) * 35);
		/* 頂いたコメントを反映して追加 「malloc不正の場合の処理」*/
		if (x[i] == NULL) {
			printf("char型ポインタのポインタ配列の要素x[%d]のメモリの動的確保に失敗しました。\n", i);
			exit(EXIT_FAILURE);
		}
		scanf_s("%s", x[i], 35);
	}

	printf("探す値:");
	ky = malloc(sizeof(char) * 35);
	/* 頂いたコメントを反映して追加 「malloc不正の場合の処理」*/
	if (x == NULL) {
		printf("探索値kyのメモリの動的確保に失敗しました。\n");
		exit(EXIT_FAILURE);
	}
	// 或いは、
	// ky = calloc(35, sizeof(char));
	scanf_s("%s", ky, 35);

	idx = search(x, nx, ky);

	if (idx == -1)
		puts("探索する値が存在しません。");
	else
		printf("%sはx[%d]にあります!!\n", ky, idx);

	/* 頂いたコメントを反映して追加(失念していた!!) 「kyのメモリ開放」*/
	free(ky);
	for (i = 0; i < nx; i++) {
		free(x[i]);
	}
	free(x);
	system("pause");		// コンソール処理が終わっても、exe画面を閉じない	。
	return 0;
}

実行結果は次のようになります。
探索成功時
char型配列 ポインタのポインタ 実行結果.png
探索失敗時
char型配列 ポインタのポインタ 実行結果_探索失敗時.png

ポイントとなるのは、
・配列をポインタで定義しようとしていること
・文字列型の配列を作りたければ、ポインタのポインタを使うこと
・メモリの動的確保(と開放)
かなと考えています。

文字列型ポインタのポインタについて!!

メモリ動的確保

ポインタのポインタとそのメモリの動的確保については、次のように図解出来ると考えています。
char型配列 ポインタのポインタ.jpg

ポインタのポインタの表記と意味

また、ポインタのポインタの表記と意味合いは、次のような図解が出来ると考えています。
char型配列 ポインタのポインタ_表記.jpg

ソースコード

参考とするソースコードを下記掲載します。

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

int main(void) {
	char** x;
	int nx;
	int i;

	printf("How many elements?:");
	scanf_s("%d", &nx);

	x = malloc(sizeof(char*) * nx);
	if (x == NULL) {
		printf("Failed malloc for char pointer to pointer.\n");
		exit(EXIT_FAILURE);
	}

	for (i = 0; i < nx; i++) {
		printf("x[%d]:", i);
		x[i] = malloc(sizeof(char) * 35);
		if (x[i] == NULL) {
			printf("Failed malloc for pointer or x[%d] which pointer to pointer points to.\n", i);
			exit(EXIT_FAILURE);
		}
		scanf_s("%s", x[i], 35);
	}

	printf("■■■■■■■■■about x[0]■■■■■■■■■\n");
	printf("▼▼▼▼▼▼the address pointer to pointer points to.▼▼▼▼▼▼\n");
	printf("&(**x) %%p : %p\n", &(**x));
	printf("x %%p : %p\n", x);
	printf("&x[0] %%p : %p\n", &x[0]);
	printf("▼▼▼▼▼▼Output with aligned notation.▼▼▼▼▼▼\n");
	printf("x[0] %%s : %s\n", x[0]);
	printf("x[0] %%p : %p\n", x[0]);
	printf("*x[0] %%c : %c\n", *x[0]);
	//printf("*x[0] %%p : %p\n", *x[0]);
	printf("*x %%s : %s\n", *x);
	printf("*x %%p : %p\n", *x);
	printf("**x %%c : %c\n", **x);
	//printf("**x %%p : %p\n", **x);
	printf("&x %%p : %p\n", &x);
	printf("&(*x[0]) %%s : %s\n", &(*x[0]));
	printf("&(*x[0]) %%p : %p\n", &(*x[0]));
	printf("&(**x) %%s : %s\n", &(**x));
	printf("&(*x) %%p : %p\n", &(*x));

	printf("■■■■■■■■■about x[1]■■■■■■■■■\n");
	printf("▼▼▼▼▼▼the address pointer to pointer points to.▼▼▼▼▼▼\n");
	printf("&(*(x + 1)) %%p : %p\n", &(*(x + 1)));
	printf("x + 1 %%p : %p\n", x + 1);
	printf("&x[1] %%p : %p\n", &x[1]);
	printf("▼▼▼▼▼▼Output with aligned notation.▼▼▼▼▼▼\n");
	printf("x[1] %%s : %s\n", x[1]);
	printf("x[1] %%p : %p\n", x[1]);
	printf("*x[1] %%c : %c\n", *x[1]);
	//printf("*x[1] %%p : %p\n", *x[1]);
	printf("*(x + 1) %%s : %s\n", *(x + 1));
	printf("*(x + 1) %%p : %p\n", *(x + 1));
	printf("**(x + 1) %%c : %c\n", **(x + 1));
	//printf("**(x + 1) %%p : %p\n", **(x + 1));
	//printf("&(x + 1) %%p : %p\n", &(x + 1));
	printf("&(*x[1]) %%s : %s\n", &(*x[1]));
	printf("&(*x[1]) %%p : %p\n", &(*x[1]));
	printf("&(**(x + 1)) %%s : %s\n", &(**(x + 1)));
	printf("&(**(x + 1)) %%p : %p\n", &(**(x + 1)));

	printf("■■■■■■■■■about x[2]■■■■■■■■■\n");
	printf("▼▼▼▼▼▼the address pointer to pointer points to.▼▼▼▼▼▼\n");
	printf("&(*(x + 2)) %%p : %p\n", &(*(x + 2)));
	printf("x + 2 %%p : %p\n", x + 2);
	printf("&x[2] %%p : %p\n", &x[2]);
	printf("▼▼▼▼▼▼Output with aligned notation.▼▼▼▼▼▼\n");
	printf("x[2] %%s : %s\n", x[2]);
	printf("x[2] %%p : %p\n", x[2]);
	printf("*x[2] %%c : %c\n", *x[2]);
	//printf("*x[2] %%p : %p\n", *x[2]);
	printf("*(x + 2) %%s : %s\n", *(x + 2));
	printf("*(x + 2) %%p : %p\n", *(x + 2));
	printf("**(x + 2) %%c : %c\n", **(x + 2));
	//printf("**(x + 2) %%p : %p\n", **(x + 2));
	//printf("&(x + 2) %%p : %p\n", &(x + 2));
	printf("&(*x[2]) %%s : %s\n", &(*x[2]));
	printf("&(*x[2]) %%p : %p\n", &(*x[2]));
	printf("&(**(x + 2)) %%s : %s\n", &(**(x + 2)));
	printf("&(**(x + 2)) %%p : %p\n", &(**(x + 2)));


	for (i = 0; i < nx; i++) {
		free(x[i]);
	}
	free(x);
	system("pause");
	return 0;
}

constの位置と影響範囲

const修飾子を付ける位置とその影響範囲についての考察を図解します。
(5番目のコメントより…Comment#5
pointer-pointer_const適用方法_modified_eng.jpg

ポインタのポインタは知識としても知っていて、その仕組みには何となく理解が及んでいたのですが、まさか、どうしても必要な場面があるとは思わず、個人的に一気に理解が捗りました!!

C言語における配列と文字列のメモリ上の特性を把握しておかなければならないと実感しています。

浅学ではございますが、以上、
char型配列の作成方法とポインタのポインタへの理解と考察でした。

コメントで数々のご指摘頂きまして、ありがとうございました。
大変勉強になります。ありがとうございます。
引き続き、何かご指摘などございましたら、頂けますと幸いです。

以上、
宜しくお願い致します。

0
2
15

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2