【C言語】
配列とポインタの違い
-処理速度比較と効率的な使い方-

■この記事の概要

この記事では、C言語における配列とポインタの違いを解説し、どちらがより効率的かを処理速度の観点から比較しています。

strcpy関数を用いた実例をもとに、配列・ポインタの各使用方法による実行時間の差を測定し、具体的なデータを示しています。


■配列vsポインタ

文字列定数を変数に設定する際、
以下のように配列とポインタで似たような記述が出来ます。
char ary[1024] = “string”;
char *ptr = “string”;

しかし似たような記述でも
プログラムの実行時間が大きく異なります。
関数ポインタを使って処理時間を比較してみましょう。

■配列[要素数有り]版

void    func1(char *p){ 
    char funcname[1024]     = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

左辺の funcname[1024]へ
右辺の文字列をコピーする命令が実行されます。

さらに余った所を1024byteになるまで0でクリアするので
処理時間が一番かかります。

■配列[要素数無し]版

void    func2(char *p){ 
    char funcname[]         = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

左辺の funcname[]へ
右辺の文字列をコピーする命令が実行されます。

右辺の文字列が長い程コピーに時間がかかります。

■ポインタ版

void    func3(char *p){
    char *funcname          = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

左辺のポインタへ
右辺の文字列のアドレスを代入しているだけななので
文字列のコピーは発生しません。

右辺の文字列が長くても短くても処理時間は同じです。

■__func__ 相当版

void    func4(char *p){ 
    static const char funcname[] = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

static が付いているので
メモリ中の文字列リテラルにfuncnameという
名前が付いた状態です。
コピー処理は実行されません。


■関数ポインタを使った処理時間計測コード

//【C言語】関数ポインタを使った配列vsポインタの処理時間計測
#include    <stdio.h>
#include    <string.h>
#include    <time.h>

//配列[要素数有り]
void    func1(char *p){ 
    char funcname[1024]     = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

//配列[要素数無し]
void    func2(char *p){ 
    char funcname[]         = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

//ポインタ
void    func3(char *p){
    char *funcname          = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

// __func__ 事前定義マクロ相当
void    func4(char *p){ 
    static const char funcname[] = "関数の名前等の固定文字列";
    strcpy(p,funcname);
}

typedef  void (*関数ポインタ_型)(char *);
static inline void    時間計測(
    int id,
    関数ポインタ_型     計測対象関数,
    char    *buf
){
    clock_t     start   = clock();
    const int   max     = 0x7fffFFF ;
    for(int i = 0;i<max ;i++){   
        計測対象関数(buf+(i%500));
    }    
    clock_t     end     = clock();
    printf("id=%d   %8ld clock\n",id, end - start);
}
char    gbuf[1024];
int main(void){
    char    abuf[1024]; 
    puts("自動変数へ出力");
    時間計測(1,func1,abuf);
    時間計測(2,func2,abuf);
    時間計測(3,func3,abuf);
    時間計測(4,func4,abuf);
    
    puts("外部変数へ出力");
    時間計測(4,func4,gbuf);
    時間計測(3,func3,gbuf);
    時間計測(2,func2,gbuf);
    時間計測(1,func1,gbuf);
}

func1()~func4()のどれが一番遅いか予想してください。
ご自分の環境でコピペしてコンパイル実行してみましょう。

●実行結果

+ gcc -O vs.c
+ ./a.out
自動変数へ出力
id=1    5250000 clock
id=2    1546875 clock
id=3     375000 clock
id=4     328125 clock
外部変数へ出力
id=4     359375 clock
id=3     375000 clock
id=2    1562500 clock
id=1    5234375 clock
+ clang -O vs.c
+ ./a.out
自動変数へ出力
id=1    3562500 clock
id=2    1468750 clock
id=3     296875 clock
id=4     328125 clock
外部変数へ出力
id=4     312500 clock
id=3     296875 clock
id=2    1484375 clock
id=1    3625000 clock

id=3の【ポインタ版】に対して
id=1の【配列[要素数有り]版】版が
10倍以上遅いのが
筆者の環境で計測されました。
ご自身の環境でも実験してみて下さい。

参考:

C-FAQ6.2: でもchar a[]はchar *aと同じと聞いたことがあるが。