C の入門書では、ポインターは多かれ少なかれ配列であると主張されることがよくあります。せいぜい、これは大幅な単純化ではないでしょうか。
Cには配列型があり、ポインターとはまったく異なる動作をすることができます。次に例を示します。
#include <stdio.h>
int main(int argc, char *argv[]){
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *b = a;
printf("sizeof(a) = %lu\n", sizeof(a));
printf("sizeof(b) = %lu\n", sizeof(b));
return 0;
}
出力を与える
sizeof(a) = 40
sizeof(b) = 8
または、別の例a = b
ではコンパイル エラーが発生します (GCC: "配列型の式への代入")。
もちろん、ポインタと配列の間には密接な関係があります。はい、配列変数自体の内容は最初の配列要素のメモリアドレスです。たとえばint a[10] = {777, 1, 2, 3, 4, 5, 6, 7, 8, 9}; printf("a = %ul\n", a);
、777 を含むアドレスを出力します。
一方では、配列を構造体に「隠す」と、=
演算子を使用するだけで大量のデータ (ラッピング構造体を無視した場合は配列) を簡単にコピーできます (それも高速です)。
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRAY_LENGTH 100000000
typedef struct {int arr[ARRAY_LENGTH];} struct_huge_array;
int main(int argc, char *argv[]){
struct_huge_array *a = malloc(sizeof(struct_huge_array));
struct_huge_array *b = malloc(sizeof(struct_huge_array));
int *x = malloc(sizeof(int)*ARRAY_LENGTH);
int *y = malloc(sizeof(int)*ARRAY_LENGTH);
struct timeval start, end, diff;
gettimeofday(&start, NULL);
*a = *b;
gettimeofday(&end, NULL);
timersub(&end, &start, &diff);
printf("Copying struct_huge_arrays took %d sec, %d µs\n", diff.tv_sec, diff.tv_usec);
gettimeofday(&start, NULL);
memcpy(x, y, ARRAY_LENGTH*sizeof(int));
gettimeofday(&end, NULL);
timersub(&end, &start, &diff);
printf("memcpy took %d sec, %d µs\n", diff.tv_sec, diff.tv_usec);
return 0;
}
出力:
Copying struct_huge_arrays took 0 sec, 345581 µs
memcpy took 0 sec, 345912 µs
ただし、配列自体でこれを行うことはできません。配列x, y
(サイズと型が同じ) の場合、式x = y
は不正です。
次に、関数は配列を返すことができません。または、配列が引数として使用されている場合、Cはそれらをポインターに折りたたみます。サイズが明示的に指定されているかどうかは気にしないため、次のプログラムは出力を提供しますsizeof(a) = 8
。
#include <stdio.h>
void f(int p[10]){
printf("sizeof(a) = %d\n", sizeof(p));
}
int main(int argc, char *argv[]){
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
f(a);
return 0;
}
この配列への嫌悪の背後にある論理はありますか? C に真に堅牢な配列型がないのはなぜですか? あるとしたらどんな悪いことが起こるでしょうか?結局のところ、配列が a に隠されている場合、配列はstruct
Go 、 Rust などのように動作します。つまり、配列はメモリ内のチャンク全体であり、それを渡すと、最初のメモリ アドレスだけでなく、その内容がコピーされます。エレメント。たとえば、次のプログラムの Go のように
package main
import "fmt"
func main() {
a := [2]int{-777, 777}
var b [2]int
b = a
b[0] = 666
fmt.Println(a)
fmt.Println(b)
}
出力を与えます:
[-777 777]
[666 777]