第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のポインタとスライスについて学びました:
次の章では、配列と文字列について学びます。