[C言語] 関数を指すポインタ [その2]

コールバック関数

#include<stdio.h>
#include<math.h>

double callback(double x){
    return x+1.0;
}

double g(double x, double (*f)(double)){
    return f(x);
}

int main(void) {
    
    double ans = g(5.0, callback); 
    printf("ans = %g\n", ans);

    ans = g(5.0, sqrt);
    printf("ans = %g\n", ans);

    return 0;
}

$ gcc main.c -o main -lm && ./main
ans = 6
ans = 2.23607

関数を呼び出す際に、引数はポインタで指定する

double callback(double x, double y){
    return x+y;
}

double g(double x, double (*f)(double,double)){
    return f(x, x);
}

int main(void) {
    
    double ans = g(5.0, callback); 
    printf("ans = %g\n", ans);

    ans = g(5.0, pow);
    printf("ans = %g\n", ans);

    return 0;
}

$ gcc main.c -o main -lm && ./main
ans = 10
ans = 3125
不思議な感じがしてくる

### シグナル処理関数
シグナルとは. プロセスやプロセスグループへ様々なイベントを通知するためにあるカーネルの機能 (ソフトウェア割り込み)。

#include<stdio.h>
#include<signal.h>

void printMessage(int sigNo){
    printf("割り込み発生:シグナル=%d\n", sigNo);
}

int main(void) {
    
    int x[1];
    int y;

    if(signal(SIGSEGV, printMessage) != SIG_ERR)
        fprintf(stderr, "シグナル設定成功\n");
    else
        fprintf(stderr, "シグナル設定失敗\n");
    y = x[10000];

    printf("プログラム終了: これは実行されません\n");

    return 0;
}

なんだこれは…

### プログラム終了時の実行関数登録

void pr1(void) {
    printf("pr1 called\n");
}
void pr2(void) {
    printf("pr2 called\n");
}
void pr3(void) {
    printf("pr3 called\n");
}

int main(void) {
    
    if(!atexit(pr1)) printf("pr1 OK\n"); else printf("pr1 No\n");
    if(!atexit(pr2)) printf("pr2 OK\n"); else printf("pr2 No\n");
    if(!atexit(pr3)) printf("pr3 OK\n"); else printf("pr3 No\n");

    return 0;
}

$ gcc main.c -o main -lm && ./main
pr1 OK
pr2 OK
pr3 OK
pr3 called
pr2 called
pr1 called
ん?

### バイナリサーチ

int compare(const int *n1, const int *n2){
    printf("%d %d\n", *n1, *n2);
    return *n1 - *n2;
}

int main(void) {
    
    int data[] = {-2, 0, 1, 3, 5, 6, 9, 10, 11, 12, 100,};
    int num = sizeof data / sizeof data[0];

    int target = 0;
    int *t;

    if((t = (int *)bsearch(&target, data, num, sizeof(data[0]), (int(*)(const void*, const void*))compare))!= NULL)
        printf("%dがありました。\n", *t);
    else
        printf("%dがありませんでした。\n", target);
    return 0;
}

$ gcc main.c -o main -lm && ./main
0 6
0 1
0 0
0がありました。
どういう流れかよくわからん…

### 文字列のバイナリサーチ

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

int compare(const char *name1, const char **name2){
    printf("%s %s\n", name1, *name2);
    return strcmp(name1, *name2);
}

int main(void) {
    
    char *names[] = {"atexit", "atol", "bsearch", "calloc", "malloc", "qsort","rand", "system", "wcstombs"};
    int num = sizeof names / sizeof names[0];

    char *name = "atol"; //検索する文字列
    char **s; // ポインタのポインタ

    if((s = bsearch(name, names, num, sizeof names[0], (int(*)(const void*, const void*))compare))!= NULL)
        printf("%sがありました。\n", *s);
    else
        printf("%sがありませんでした。\n", name);
    return 0;
}

$ gcc main.c -o main -lm && ./main
atol malloc
atol bsearch
atol atol
atolがありました。

strcmpでcompareしているのはわかるが、なぜ連続になるのだろうか…

### クイックソート

int compare(const int *n1, const int *n2){
    return *n1 - *n2;
}

int main(void) {
    
    int data[] = {4, 2, -2, 8, -9, 6, 4, 2, 1, 0, 17, -3, 23};
    int num = sizeof data / sizeof data[0];

    int k;

    qsort(data, num, sizeof(data[0]), (int(*)(const void*, const void*)) compare);

    for(k=0; k<num; k++)
        printf("%d ", data[k]);
    printf("\n");
    return 0;
}

$ gcc main.c -o main -lm && ./main
-9 -3 -2 0 1 2 2 4 4 6 8 17 23

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

int compare( const char **name1, const char **name2 ) /* 比較関数 */
{
	printf( "%s, %s\n", *name1, *name2 ); /* テスト用表示:実際には不要 */
	return strcmp( *name1, *name2 );
}

int main( void )
{
	char *names[] = { "rand", "calloc", "malloc", "wcstombs", "atol", "qsort", "atexit", "system", "bsearch", };
	int  num = sizeof names / sizeof names[0];

	int k;

	qsort( names, num, sizeof( names[0] ), (int (*)(const void *, const void * ))compare );

	for( k=0; k<num; k++ ) /* 結果の表示 */
		printf( "%s ", names[k] );
	printf( "\n" );

	return 0;
}

$ gcc main.c -o main -lm && ./main
rand, calloc
malloc, wcstombs
calloc, malloc
rand, malloc
rand, wcstombs
atol, qsort
system, bsearch
atexit, bsearch
atol, atexit
atol, bsearch
qsort, bsearch
qsort, system
calloc, atexit
calloc, atol
calloc, bsearch
calloc, qsort
malloc, qsort
rand, qsort
rand, system
wcstombs, system
atexit atol bsearch calloc malloc qsort rand system wcstombs

[C言語] 関数を指すポインタ

double f1(double x) {
    return x+1.0;
}

int main(void) {
    
    double (*p)(double) = f1; // ポインタpに関数fをセット

    double ans = p(5.0);

    printf("p(5.0)=%g\n", ans);

    return 0;
}

$ gcc main.c -o main && ./main
p(5.0)=6

ポインタは値、配列だけでなく、関数も指すことができる

#include<stdio.h>
#include<math.h>

double f1(double x) {
    return x+1.0;
}

double f2(double x) {
    return x+2.0;
}

int main(void) {
    
    double (*p)(double) = f1; // ポインタpに関数fをセット

    double ans1 = p(5.0);
    double ans2, ans3;

    p = f2;

    ans2 = p(5.0);

    p = sqrt;
    ans3 = p(2.0);

    printf("ans1=%g, ans2=%g, ans3=%g\n", ans1, ans2, ans3); // 6.0, 7.0, 4.0

    return 0;
}

$ gcc main.c -o main -lm && ./main
ans1=6, ans2=7, ans3=1.41421
コンパイル時に-lmオプションがないとエラーになる

double f1(double x) {
    return x+1.0;
}

double f2(double x) {
    return x+2.0;
}

double f3(double x) {
    return x+3.0;
}

int main(void) {
    
    double (*p[3])(double) = {f1, f2, f3}; // ポインタpに関数fをセット
    int k;
    for (k=0; k<3; k++){
        double ans = p[k](5.0);
        printf("p[%d](5.0)=%g\n", k, ans); // 6.0, 7.0, 8.0
    }

    return 0;
}

$ gcc main.c -o main -lm && ./main
p[0](5.0)=6
p[1](5.0)=7
p[2](5.0)=8

これはそのまま

typedef double(*funcTable[3])(double); // 関数を指すポインタの配列型

int main(void) {
    
    funcTable p = {f1, f2, f3};
    int k;
    for (k=0; k<3; k++){
        double ans = p[k](5.0);
        printf("p[%d](5.0)=%g\n", k, ans); // 6.0, 7.0, 8.0
    }

    return 0;
}

このように書くことができる

[C言語] ポインタの派生型

### ポインタのポインタ

int main(void) {
    
    int p0 = 12345;
    int *p1;
    int **p2; // ポインタのポインタ
    int ***p3; // ポインタのポインタのポインタ
    int t1, t2, t3;

    p1 = &p0;
    p2 = &p1;
    p3 = &p2;

    t1 = *p1;
    t2 = **p2;
    t3 = ***p3;

    printf("t1=%d, t2=%d, t3=%d", t1, t2, t3);


    return 0;
}

$ gcc main.c -o main && ./main
t1=12345, t2=12345, t3=12345
全てp0の値を出力する

### 配列を指すポインタ

int main(void) {
    
    int t[3] = {1, 2, 3};
    int (*p)[3];
    int t1;

    p = &t;
    t1 = (*p)[1];
    printf("t1=%d\n", t1);

    return 0;
}

$ gcc main.c -o main && ./main
t1=2
配列は通常の配列だけでなく、ポインタも配列として表現できる

int main(void) {
    
    int a[2][3][4] = {
        {{1,2,3,4}, {5,6,7,8},{9,10,11,12}},
        {{13,14,15,16}, {17,18,19,20},{21,22,23,24}}
    };
    int *b;
    int (*c)[4];
    int (*d)[3][4];

    int a1, a2, a3;

    b = a[1][2]; // 21,22,23,24 の先頭アドレス
    c = a[1]; // {13,14,15,16}, {17,18,19,20},{21,22,23,24}の先頭アドレス
    d = a; // aの先頭アドレス

    a1 = b[3]; // 24
    a2 = c[1][2]; // 19
    a3 = d[0][1][2]; // 7 

    printf("a[1][2][3] = %d\n", a[1][2][3]); // 24
    printf("a1=%d, a2=%d, a3=%d\n", a1, a2, a3); // 24, 19, 7

    return 0;
}

$ gcc main.c -o main && ./main
a[1][2][3] = 24
a1=24, a2=19, a3=7

ポインタは配列だけでなく、連想配列としても宣言できる。
想定通りの時の高揚感が良い

int main(void) {
    
    int k1=1, k2=2, k3=3;
    int *p[3] = {&k1, &k2, &k3};
    int t;

    for (t=0; t<3; t++){
        int value = *p[t];
        printf("value=%d\n", value); // 1, 2, 3
    }

    return 0;
}

$ gcc main.c -o main && ./main
value=1
value=2
value=3
for文にしただけ

int main(void) {
    
    int k;
    char *mes[] = {"one", "two", "three", (char *) NULL};

    for(k=0; mes[k]; k++){
        printf("mes=%s, ", mes[k]); // one two three
    }

    return 0;
}

$ gcc main.c -o main && ./main
mes=one, mes=two, mes=three,

for文でmes[k]となっているので、kに値があるまでで(char *) NULLは出力されない

[C言語] ポインタの利用

#include<stdio.h>

int main(void) {
    int *p;
    int k = 123456;
    int t;

    p = &k;
    t = *p;

    printf("p=%p, t=%d\n", p, t);

    return 0;
}

$ gcc main.c -o main && ./main
p=0xfffff6a796e8, t=123456

&kはアドレスで、ポインタを代入は値の代入になる

### 割り当てられていない領域をアクセス

int main(void) {
    int *p;
    int k = 123456;
    int t;

    p = &k;
    p++;
    t = *p;

    printf("p=%p, t=%d\n", p, t);

    return 0;
}

p=0xffffd0f0d40c, t=65535
pのアドレスを一つ進めているので、異なる値が表示される

### 割り当てられている領域をアクセス

int main(void) {
    int *p;
    int k[2] = {12345, 67890}; // 配列
    int t;

    p = &k[0];
    p++;
    t = *p;

    printf("p=%p, t=%d\n", p, t);

    return 0;
}

$ gcc main.c -o main && ./main
p=0xfffff62356e4, t=67890
配列の場合は、ポインタを一つ進めると、配列の次の値となる。

int main(void) {
    int *p;
    int k[2] = {12345, 67890}; // 配列
    int t;

    p = k;
    p++;
    t = *p;

    printf("p=%p, t=%d\n", p, t);

    return 0;
}

$ gcc main.c -o main && ./main
p=0xffffecd87384, t=67890

配列kは、kの先頭アドレスを指す

int main(void) {
    int *p;
    int k[3] = {111, 222, 333}; // 配列
    int t;

    p = &k[1];
    p++;
    t = *p;

    printf("p=%p, t=%d\n", p, t);

    return 0;
}

$ gcc main.c -o main && ./main
p=0xffffc27bcfc0, t=333

これは説明不要

int main(void) {
    int *p;
    int k[3] = {111, 222, 333}; // 配列
    int i;

    p = k;
    for(i =0; i < 3; i++){
        int value = *p++;
        printf("value=%d\n", value);
    }

    return 0;
}

$ gcc main.c -o main && ./main
value=111
value=222
value=333

配列の先頭アドレスから出力している
配列をポインタに代入して、ポインタを進めることで、配列の値を出力するというのは多用できそうですね。

[c言語]calloc

calloc関数は、大きさがsizeであるオブジェクトのnmemb個の配列の領域を割りあて
確保した先頭のメモリのポインタを返却

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

void memdump(void *p);

int main(int argc, char*argv[]){
    void *p = NULL;

    p = calloc((size_t)10, (size_t)32);
    if(p == NULL) {
        fprintf(stdout, "calloc 10 * 32 byte error!\n");
        return -1;
    }
    memdump(p);
    free(p);
    p = calloc((size_t)10, (size_t)32*1024);
    if(p == NULL) {
        fprintf(stdout, "calloc 10 * 32kbyte error!\n");
        return -1;
    }
    memdump(p);
    free(p);
    p = calloc((size_t)10, (size_t)32*1024*1024);
    if(p == NULL) {
        fprintf(stdout, "calloc 10 * 32Mbyte error!\n");
        return -1;
    }
    memdump(p);
    free(p);
}

void memdump(void *p){
    fprintf(stdout, "==== memdump start ====\n");
    unsigned char *cp = NULL;
    int i = 0;
    for(cp = (char*)p, i = 0; i < 4; cp++, i++){
        fprintf(stdout, "cp[%p]:%02x\n", cp, *cp);
    }
    fprintf( stdout, "==== memdump end ====\n\n" );
}

個数とバイトサイズを指定して確保する

cポインタとassembler

#include <stdio.h>

int main(void) {

    int a = 75;
    long addrA = (long)&a;
    printf("%ld\n", addrA);
    return 0;
}
|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x20]!
        mov         fp,sp
        mov         w8,#0x4B
        str         w8,[sp,#0x10]
        add         x8,sp,#0x10
        mov         w8,w8
        str         w8,[sp,#0x14]
        ldr         w1,[sp,#0x14]
        adrp        x8,|$SG4983|
        add         x0,x8,|$SG4983|
        bl          printf
        mov         w0,#0
        ldp         fp,lr,[sp],#0x20
        ret

str w8,[sp,#0x14] としてるのは、先頭アドレスを保存しているっぽいね。

ポインタは

int main(void) {

    int a = 75;
    int* addrA = (void*)&a;
    printf("%p\n", addrA);
    return 0;
}
|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x20]!
        mov         fp,sp
        mov         w8,#0x4B
        str         w8,[sp,#0x10]
        add         x8,sp,#0x10
        str         x8,[sp,#0x18]
        ldr         x1,[sp,#0x18]
        adrp        x8,|$SG4983|
        add         x0,x8,|$SG4983|
        bl          printf
        mov         w0,#0
        ldp         fp,lr,[sp],#0x20
        ret

アドレス&aとポインタ*addrAはそれぞれメモリ上別の領域に格納されて、printfでは*addrAのアドレスが呼ばれていますね。

Cで困ったらコンパイルした後のアッセンブラを見れば良いのかな。

cの文字列とassembler

#include <stdio.h>

typedef char String[1024];

int main(void) {

    String name = "ドラゴン";
    printf("私は%sです\n", name);
    return 0;
}
|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        bl          __security_push_cookie
        sub         sp,sp,#0x400
        add         x8,sp,#8
        str         x8,[sp]
        ldr         x9,[sp]
        adrp        x8,|$SG4982|
        add         x8,x8,|$SG4982|
        ldr         x10,[x8]
        str         x10,[x9]
        ldr         w10,[x8,#8]
        str         w10,[x9,#8]
        ldrsb       w8,[x8,#0xC]
        strb        w8,[x9,#0xC]
        add         x0,sp,#0x15
        mov         x2,#0x3F3
        mov         w1,#0
        bl          memset
        add         x1,sp,#8
        adrp        x8,|$SG4983|
        add         x0,x8,|$SG4983|
        bl          printf
        mov         w0,#0
        add         sp,sp,#0x400
        bl          __security_pop_cookie
        ldp         fp,lr,[sp],#0x10
        ret

なぜ64ビットのx10と32ビットのw8を使用しているか不明
blはサブルーチン呼び出し

[C言語]複数ファイルのコンパイル&実行

main.c

#include <stdio.h>

int main() {
    double a, b, sum(double, double), product(double, double);
    printf("input two numbers: ");
    scanf("%lf%lf", &a, &b);
    printf("sum=%g\n"; sum(a, b));
    printf("product=%g\n", product(a, b));
    return 0;
}

sub1.c

double sum(double a, double b){
    return a + b;
}

sub2.c

double product(double a, double b){
    return a * b;
}

$ gcc -o sum_product main.c sub1.c sub2.c
$ ./sum_product
input two numbers: 2 3
sum=5
product=6

main.cからはheader情報は特になし
gccで複数ファイルから一つにコンパイルする

[C++/C] 型, 変数, キーボードからの入力, 定数

基本型
bool, char, unsigned char(符号なし), short int, unsigned short int, int, unsigned int, long int, unsigned long int, float, double, long double

bitとbytes
2進数の1桁分をbitと呼ぶ
00101110

2進数の8桁の数値はbyteと呼ぶ

### 変数の利用

#include <iostream>
using namespace std;

int main() {
    int num;
    num = 3;

    cout << "変数numの値は" << num << "です。\n";

    return 0;
}

変数の値を変更

int main() {
    int num = 3;
    cout << "変数numの値は" << num << "です。\n";

    num = 5;

    cout << "新しい変数numの値は" << num << "です。\n";

    return 0;
}

他の変数の値を代入

int main() {
    int num1, num2;
    num1 = 3;

    cout << "変数num1の値は" << num1 << "です。\n";

    num2 = num1;

    cout << "変数num2の値は" << num2 << "です。\n";

    return 0;
}

値の代入と型変換

int main() {
    int num1;
    double num2;

    num1 = 3.14;
    num2 = 3.14;

    cout << "変数num1の値は" << num1 << "です。\n";
    cout << "変数num2の値は" << num2 << "です。\n";

    return 0;
}

$ g++ -o sample sample.cpp && ./sample
変数num1の値は3です。
変数num2の値は3.14です。
// 小数点以下は切り捨てられる

キーボードからの入力
=> cin >> 変数; と記載する

int main() {
    int num = 0;

    cout << "整数を入力してください。\n";

    cin >> num;

    cout << num << "が入力されました。\n";

    return 0;
}

2つ以上の数値の入力

int main() {
    int num1, num2;

    cout << "整数を2つ入力してください。\n";

    cin >> num1 >> num2;

    cout << "最初に" << num1 << "が入力されました。\n";
    cout << "次に" << num2 << "が入力されました。\n";

    return 0;
}

定数はconstを指定する
初期化によって変数を変更できないようにする

int main() {
    const double pi = 3.1415;

    cout << "円周率の値は" << pi << "です。\n";

    return 0;
}

practice 1

int main() {
    double pi;

    cout << "円周率の値は幾つですか?\n";

    cin >> pi;

    cout << "円周率の値は" << pi << "です。\n";

    return 0;
}

practice 2

int main() {
    char str;

    cout << "アルファベットの最初の文字は何ですか?\n";

    cin >> str;

    cout << "アルファベットの最初の文字は" << str << "です。\n";

    return 0;
}

practice 3

int main() {
    double height, weight;

    cout << "身長と体重を入力してください。\n";

    cin >> height >> weight;

    cout << "身長は" << height << "です。\n";
    cout << "体重は" << weight << "です。\n";

    return 0;
}

c++では文字列の変数名はstrよりもchの方が型との違いがなく表現としては望ましそうです。

[C++/C] ポインタ

ポインタは変数のアドレスを持つ変数のこと
変数のアドレス場所を表すには、変数の頭に&をつける

ポインタ型とは、変数のアドレスを保存しておくための型
下記は同じ意味

char* test;
char *test;
#include <stdio.h>

int main(void) {

    char buf[50] = "bitcoin";
    char *test;

    test = buf;
    printf("%s", test);
    return 0;
}

文字列の後ろにつけるアスタリスクがあるのかと勘違いしてしまいますね。