第7章: ポインタとスライス

学習目標

この章を終えると、以下ができるようになります:

  • ポインタの基本的な使い方を理解する
  • スライスを効果的に使用できる
  • センチネル終端配列を理解する
  • メモリ安全なコードを書ける
  • ポインタの基本

    ポインタの作成と参照外し

    const std = @import("std");
    
    pub fn example() void {
        var x: i32 = 42;
    
        // ポインタの作成
        const ptr: *i32 = &x;
    
        // ポインタの参照外し
        std.debug.print("Value: {}\n", .{ptr.*});
    
        // ポインタ経由で値を変更
        ptr.* = 100;
        std.debug.print("Modified: {}\n", .{x});
    }
    

    const ポインタと var ポインタ

    const std = @import("std");
    
    pub fn example() void {
        var x: i32 = 42;
    
        // const ポインタ(値を変更できない)
        const const_ptr: *const i32 = &x;
        // const_ptr.* = 100; // コンパイルエラー!
        std.debug.print("Value: {}\n", .{const_ptr.*});
    
        // var ポインタ(値を変更できる)
        const var_ptr: *i32 = &x;
        var_ptr.* = 100; // OK
        std.debug.print("Modified: {}\n", .{x});
    }
    

    ポインタのサイズ

    const std = @import("std");
    
    pub fn example() void {
        std.debug.print("Pointer size: {} bytes\n", .{@sizeOf(*i32)});
        std.debug.print("usize size: {} bytes\n", .{@sizeOf(usize)});
    }
    

    多重ポインタ

    ポインタのポインタ

    const std = @import("std");
    
    pub fn example() void {
        var x: i32 = 42;
        var ptr1: *i32 = &x;
        var ptr2: **i32 = &ptr1;
    
        std.debug.print("Value: {}\n", .{ptr2.*.*});
    
        // 二重参照外しで値を変更
        ptr2.*.* = 100;
        std.debug.print("Modified: {}\n", .{x});
    }
    

    配列ポインタ

    配列全体へのポインタ

    const std = @import("std");
    
    pub fn example() void {
        var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // 配列全体へのポインタ
        const arr_ptr: *[5]i32 = &numbers;
    
        // 配列要素へのアクセス
        std.debug.print("First: {}\n", .{arr_ptr[0]});
        std.debug.print("Second: {}\n", .{arr_ptr[1]});
    
        // 配列要素の変更
        arr_ptr[0] = 100;
        std.debug.print("Modified: {}\n", .{numbers[0]});
    }
    

    多要素ポインタ

    const std = @import("std");
    
    pub fn example() void {
        var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // 多要素ポインタ(長さ情報なし)
        const many_ptr: [*]i32 = &numbers;
    
        // インデックスでアクセス
        std.debug.print("First: {}\n", .{many_ptr[0]});
        std.debug.print("Second: {}\n", .{many_ptr[1]});
    
        // ポインタ演算
        const ptr_plus_2 = many_ptr + 2;
        std.debug.print("Third: {}\n", .{ptr_plus_2[0]});
    }
    

    スライス

    スライスの基本

    const std = @import("std");
    
    pub fn example() void {
        var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // スライスの作成
        const slice: []i32 = &numbers;
    
        // 長さの取得
        std.debug.print("Length: {}\n", .{slice.len});
    
        // 要素へのアクセス
        for (slice, 0..) |num, i| {
            std.debug.print("[{}] = {}\n", .{i, num});
        }
    }
    

    部分スライス

    const std = @import("std");
    
    pub fn example() void {
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // 範囲指定のスライス
        const slice1 = numbers[1..4];  // [2, 3, 4]
        const slice2 = numbers[0..3];  // [1, 2, 3]
        const slice3 = numbers[2..];   // [3, 4, 5]
    
        std.debug.print("Slice 1: ", .{});
        for (slice1) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    
        std.debug.print("Slice 2: ", .{});
        for (slice2) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    
        std.debug.print("Slice 3: ", .{});
        for (slice3) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    }
    

    const スライスと var スライス

    const std = @import("std");
    
    pub fn example() void {
        var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // const スライス(要素を変更できない)
        const const_slice: []const i32 = &numbers;
        // const_slice[0] = 100; // コンパイルエラー!
    
        // var スライス(要素を変更できる)
        const var_slice: []i32 = &numbers;
        var_slice[0] = 100; // OK
    
        std.debug.print("Modified: {}\n", .{numbers[0]});
    }
    

    センチネル終端配列

    null終端文字列

    const std = @import("std");
    
    pub fn example() void {
        // センチネル終端配列(C言語の文字列と互換)
        const str: [*:0]const u8 = "Hello, Zig!";
    
        std.debug.print("String: {s}\n", .{str});
    
        // 長さの計算
        var len: usize = 0;
        while (str[len] != 0) : (len += 1) {}
        std.debug.print("Length: {}\n", .{len});
    }
    

    センチネルスライス

    const std = @import("std");
    
    pub fn example() void {
        const str = "Hello, Zig!";
    
        // センチネルスライス
        const sentinel_slice: [:0]const u8 = str;
    
        std.debug.print("String: {s}\n", .{sentinel_slice});
        std.debug.print("Length: {}\n", .{sentinel_slice.len});
        std.debug.print("Sentinel: {}\n", .{sentinel_slice[sentinel_slice.len]});
    }
    

    メモリアロケーション

    動的メモリ割り当て

    const std = @import("std");
    
    pub fn example() !void {
        const allocator = std.heap.page_allocator;
    
        // 単一の値を割り当て
        const ptr = try allocator.create(i32);
        defer allocator.destroy(ptr);
    
        ptr.* = 42;
        std.debug.print("Value: {}\n", .{ptr.*});
    
        // 配列を割り当て
        const array = try allocator.alloc(i32, 10);
        defer allocator.free(array);
    
        for (array, 0..) |*item, i| {
            item.* = @intCast(i * 10);
        }
    
        std.debug.print("Array: ", .{});
        for (array) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    }
    

    アロケータの種類

    const std = @import("std");
    
    pub fn example() !void {
        // GeneralPurposeAllocator(デバッグに適している)
        var gpa = std.heap.GeneralPurposeAllocator(.{}){};
        defer _ = gpa.deinit();
        const allocator = gpa.allocator();
    
        const buffer = try allocator.alloc(u8, 1024);
        defer allocator.free(buffer);
    
        std.debug.print("Allocated {} bytes\n", .{buffer.len});
    }
    

    ArenaAllocator

    const std = @import("std");
    
    pub fn example() !void {
        var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
        defer arena.deinit(); // すべてのメモリを一度に解放
    
        const allocator = arena.allocator();
    
        // 複数の割り当て
        const arr1 = try allocator.alloc(i32, 10);
        const arr2 = try allocator.alloc(i32, 20);
        const arr3 = try allocator.alloc(i32, 30);
    
        // arena.deinit() で全て解放される
        std.debug.print("Allocated 3 arrays\n", .{});
    }
    

    ポインタの演算

    ポインタの加算と減算

    const std = @import("std");
    
    pub fn example() void {
        var numbers = [_]i32{ 10, 20, 30, 40, 50 };
        const ptr: [*]i32 = &numbers;
    
        std.debug.print("ptr[0] = {}\n", .{ptr[0]});
        std.debug.print("ptr[1] = {}\n", .{ptr[1]});
    
        // ポインタの加算
        const ptr_plus_2 = ptr + 2;
        std.debug.print("(ptr+2)[0] = {}\n", .{ptr_plus_2[0]});
    
        // ポインタの減算
        const diff = ptr_plus_2 - ptr;
        std.debug.print("Difference: {}\n", .{diff});
    }
    

    ポインタのアライメント

    const std = @import("std");
    
    pub fn example() void {
        var x: i32 align(16) = 42;
    
        const ptr: *align(16) i32 = &x;
    
        std.debug.print("Value: {}\n", .{ptr.*});
        std.debug.print("Alignment: {}\n", .{@alignOf(@TypeOf(ptr.*))});
    }
    

    実践例

    例1: スライス操作

    const std = @import("std");
    
    fn reverseSlice(comptime T: type, slice: []T) void {
        var left: usize = 0;
        var right: usize = slice.len - 1;
    
        while (left < right) {
            const temp = slice[left];
            slice[left] = slice[right];
            slice[right] = temp;
    
            left += 1;
            right -= 1;
        }
    }
    
    pub fn main() void {
        var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        std.debug.print("Original: ", .{});
        for (numbers) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    
        reverseSlice(i32, &numbers);
    
        std.debug.print("Reversed: ", .{});
        for (numbers) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    }
    

    例2: メモリプール

    const std = @import("std");
    
    const MemoryPool = struct {
        buffer: []u8,
        offset: usize,
    
        pub fn init(buffer: []u8) MemoryPool {
            return MemoryPool{
                .buffer = buffer,
                .offset = 0,
            };
        }
    
        pub fn alloc(self: *MemoryPool, size: usize) ?[]u8 {
            if (self.offset + size > self.buffer.len) {
                return null;
            }
    
            const result = self.buffer[self.offset..self.offset + size];
            self.offset += size;
            return result;
        }
    
        pub fn reset(self: *MemoryPool) void {
            self.offset = 0;
        }
    };
    
    pub fn main() void {
        var buffer: [1024]u8 = undefined;
        var pool = MemoryPool.init(&buffer);
    
        if (pool.alloc(100)) |mem1| {
            std.debug.print("Allocated {} bytes\n", .{mem1.len});
        }
    
        if (pool.alloc(200)) |mem2| {
            std.debug.print("Allocated {} bytes\n", .{mem2.len});
        }
    
        std.debug.print("Total used: {} bytes\n", .{pool.offset});
    
        pool.reset();
        std.debug.print("Pool reset\n", .{});
    }
    

    例3: 文字列操作

    const std = @import("std");
    
    fn stringLength(str: [*:0]const u8) usize {
        var len: usize = 0;
        while (str[len] != 0) : (len += 1) {}
        return len;
    }
    
    fn stringCopy(dest: [*]u8, src: [*:0]const u8) void {
        var i: usize = 0;
        while (src[i] != 0) : (i += 1) {
            dest[i] = src[i];
        }
        dest[i] = 0;
    }
    
    pub fn main() void {
        const src: [*:0]const u8 = "Hello, Zig!";
        var dest: [100]u8 = undefined;
    
        const len = stringLength(src);
        std.debug.print("Length: {}\n", .{len});
    
        stringCopy(&dest, src);
        std.debug.print("Copied: {s}\n", .{dest[0..len]});
    }
    

    安全性のガイドライン

    メモリ安全性のベストプラクティス

    const std = @import("std");
    
    pub fn safePractices() !void {
        const allocator = std.heap.page_allocator;
    
        // 1. 必ず defer で解放
        const buffer = try allocator.alloc(u8, 1024);
        defer allocator.free(buffer);
    
        // 2. スライスの範囲チェック
        if (buffer.len > 100) {
            buffer[100] = 42; // 安全
        }
    
        // 3. オプショナルポインタを使用
        var maybe_ptr: ?*i32 = null;
        if (maybe_ptr) |ptr| {
            std.debug.print("Value: {}\n", .{ptr.*});
        }
    }
    

    まとめ

    この章では、Zigのポインタとスライスについて学びました:

  • ポインタ: 基本的な使い方、const/var ポインタ、多重ポインタ
  • 配列ポインタ: 配列全体へのポインタ、多要素ポインタ
  • スライス: 基本操作、部分スライス、const/var スライス
  • センチネル終端: null終端文字列、センチネルスライス
  • メモリ管理: アロケータ、動的メモリ割り当て
  • 次の章では、配列と文字列について学びます。

    参考資料

  • Zig Language Reference - Pointers: https://ziglang.org/documentation/master/#Pointers
  • Zig Language Reference - Slices: https://ziglang.org/documentation/master/#Slices
  • Ziglearn - Pointers: https://ziglearn.org/chapter-2/#pointers