課題15: C言語連携
マンダトリー要件 (80点)
Part 1: Cヘッダーのインポート (20点)
以下の要件を満たすプログラムを作成してください:
ファイル名: part1_import.zig
const std = @import("std");
const c = @cImport({
// TODO: 必要なヘッダーをインポート
});
// TODO: 以下の関数を実装してください
// 1. 標準出力にメッセージを出力 (C の printf を使用)
pub fn printMessage(message: []const u8) void {
// ここに実装
}
// 2. 平方根を計算 (C の sqrt を使用)
pub fn calculateSqrt(x: f64) f64 {
// ここに実装
}
// 3. 文字列の長さを取得 (C の strlen を使用)
pub fn getStringLength(s: []const u8) usize {
// ここに実装
}
// 4. メモリを動的に割り当て、値をコピー (C の malloc/memcpy を使用)
pub fn duplicateData(data: []const u8) ?[*]u8 {
// ここに実装
// 注意: 呼び出し側で free が必要
}
pub fn main() void {
printMessage("Hello from C!");
const sqrt_result = calculateSqrt(16.0);
std.debug.print("sqrt(16) = {d:.2}\n", .{sqrt_result});
const test_str = "Hello, World!";
const len = getStringLength(test_str);
std.debug.print("Length: {}\n", .{len});
const dup = duplicateData(test_str);
if (dup) |ptr| {
defer c.free(ptr);
std.debug.print("Duplicated: {s}\n", .{ptr[0..len]});
}
}
要件:
@cImportと@cIncludeを正しく使用- 各関数が正しく動作すること
- メモリリークが発生しないこと
Part 2: POSIX APIの活用 (20点)
ファイル操作をPOSIX APIで実装してください:
ファイル名: part2_posix.zig
const std = @import("std");
const c = @cImport({
// TODO: POSIXヘッダーをインポート
});
pub const FileError = error{
OpenFailed,
ReadFailed,
WriteFailed,
CloseFailed,
};
// TODO: 以下の関数を実装してください
// 1. ファイルを作成し、データを書き込む
pub fn writeFile(path: []const u8, data: []const u8) FileError!void {
// ここに実装
// open(), write(), close() を使用
}
// 2. ファイルからデータを読み込む
pub fn readFile(allocator: std.mem.Allocator, path: []const u8) FileError![]u8 {
// ここに実装
// open(), read(), close() を使用
}
// 3. ファイルの情報を取得
pub fn getFileSize(path: []const u8) FileError!usize {
// ここに実装
// stat() を使用
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
// ファイルに書き込み
const test_data = "Hello, POSIX!\nThis is a test file.\n";
try writeFile("test_posix.txt", test_data);
std.debug.print("File written successfully\n", .{});
// ファイルサイズを取得
const size = try getFileSize("test_posix.txt");
std.debug.print("File size: {} bytes\n", .{size});
// ファイルを読み込み
const content = try readFile(allocator, "test_posix.txt");
defer allocator.free(content);
std.debug.print("File content:\n{s}\n", .{content});
}
要件:
- POSIX APIを正しく使用
- エラーハンドリングが適切であること
- リソース(ファイルディスクリプタ)が正しく解放されること
Part 3: C互換関数のエクスポート (20点)
C言語から呼び出し可能な数学ライブラリを作成してください:
ファイル名: part3_export.zig
const std = @import("std");
// TODO: 以下のC互換関数を実装してください
// 1. 2つの整数の最大公約数を求める
export fn gcd(a: c_int, b: c_int) c_int {
// ここに実装
}
// 2. 2つの整数の最小公倍数を求める
export fn lcm(a: c_int, b: c_int) c_int {
// ここに実装
}
// 3. 階乗を計算
export fn factorial(n: c_int) c_longlong {
// ここに実装
}
// 4. nの累乗を計算
export fn power(base: c_int, exp: c_int) c_longlong {
// ここに実装
}
// 5. 配列の要素を合計
export fn sumArray(arr: [*]const c_int, len: c_int) c_int {
// ここに実装
}
// テスト用のmain関数
pub fn main() void {
std.debug.print("gcd(48, 18) = {}\n", .{gcd(48, 18)});
std.debug.print("lcm(12, 18) = {}\n", .{lcm(12, 18)});
std.debug.print("factorial(5) = {}\n", .{factorial(5)});
std.debug.print("power(2, 10) = {}\n", .{power(2, 10)});
const arr = [_]c_int{ 1, 2, 3, 4, 5 };
std.debug.print("sum([1,2,3,4,5]) = {}\n", .{sumArray(&arr, arr.len)});
}
Cヘッダーファイルも作成: mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H
#ifdef __cplusplus
extern "C" {
#endif
int gcd(int a, int b);
int lcm(int a, int b);
long long factorial(int n);
long long power(int base, int exp);
int sumArray(const int* arr, int len);
#ifdef __cplusplus
}
#endif
#endif // MATHLIB_H
要件:
- すべての関数が
exportキーワードを使用 - C互換の型を使用
- 対応するヘッダーファイルを作成
Part 4: Cライブラリのラッピング (20点)
システムの時刻関連APIをラップするライブラリを作成してください:
ファイル名: part4_wrapper.zig
const std = @import("std");
const c = @cImport({
// TODO: 時刻関連のヘッダーをインポート
});
pub const TimeError = error{
GetTimeFailed,
FormatFailed,
};
pub const DateTime = struct {
year: u32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
// TODO: 以下のメソッドを実装してください
// 1. 現在の日時を取得
pub fn now() TimeError!DateTime {
// ここに実装
// time(), localtime() を使用
}
// 2. Unix時刻から変換
pub fn fromTimestamp(timestamp: i64) TimeError!DateTime {
// ここに実装
}
// 3. Unix時刻に変換
pub fn toTimestamp(self: DateTime) i64 {
// ここに実装
// mktime() を使用
}
// 4. フォーマットした文字列を取得
pub fn format(
self: DateTime,
allocator: std.mem.Allocator,
) std.mem.Allocator.Error![]u8 {
// ここに実装
// 形式: "YYYY-MM-DD HH:MM:SS"
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
// 現在時刻を取得
const now = try DateTime.now();
const formatted = try now.format(allocator);
defer allocator.free(formatted);
std.debug.print("Current time: {s}\n", .{formatted});
// Unix時刻との相互変換
const timestamp = now.toTimestamp();
std.debug.print("Unix timestamp: {}\n", .{timestamp});
const from_ts = try DateTime.fromTimestamp(timestamp);
const formatted2 = try from_ts.format(allocator);
defer allocator.free(formatted2);
std.debug.print("From timestamp: {s}\n", .{formatted2});
}
要件:
- CのAPIをZigの型システムでラップ
- エラーハンドリングを適切に実装
- メモリ管理が正しく行われること
ボーナス課題 (20点)
Bonus 1: SQLiteラッパー (10点)
SQLiteデータベースのラッパーライブラリを作成してください:
ファイル名: bonus1_sqlite.zig
const std = @import("std");
const c = @cImport({
@cInclude("sqlite3.h");
});
pub const SqliteError = error{
OpenFailed,
PrepareFailed,
BindFailed,
StepFailed,
ExecuteFailed,
};
pub const Database = struct {
db: ?*c.sqlite3,
pub fn open(path: []const u8) SqliteError!Database {
// TODO: 実装
}
pub fn close(self: *Database) void {
// TODO: 実装
}
pub fn execute(self: Database, sql: []const u8) SqliteError!void {
// TODO: 実装
}
};
pub const Statement = struct {
stmt: ?*c.sqlite3_stmt,
pub fn prepare(db: Database, sql: []const u8) SqliteError!Statement {
// TODO: 実装
}
pub fn bindInt(self: Statement, index: c_int, value: i32) SqliteError!void {
// TODO: 実装
}
pub fn bindText(self: Statement, index: c_int, text: []const u8) SqliteError!void {
// TODO: 実装
}
pub fn step(self: Statement) SqliteError!bool {
// TODO: 実装
}
pub fn getInt(self: Statement, index: c_int) i32 {
// TODO: 実装
}
pub fn getText(self: Statement, allocator: std.mem.Allocator, index: c_int) ![]u8 {
// TODO: 実装
}
pub fn finalize(self: *Statement) void {
// TODO: 実装
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
var db = try Database.open("test.db");
defer db.close();
// テーブル作成
try db.execute(
\\CREATE TABLE IF NOT EXISTS users (
\\ id INTEGER PRIMARY KEY,
\\ name TEXT NOT NULL,
\\ age INTEGER
\\);
);
// データ挿入
var stmt = try Statement.prepare(db, "INSERT INTO users (name, age) VALUES (?, ?)");
defer stmt.finalize();
try stmt.bindText(1, "Alice");
try stmt.bindInt(2, 30);
_ = try stmt.step();
std.debug.print("Database operations completed\n", .{});
}
要件:
- データベースの開閉
- SQL実行
- プリペアドステートメント
- パラメータバインディング
- 結果の取得
Bonus 2: HTTPクライアント (10点)
libcurlを使用したHTTPクライアントを作成してください:
ファイル名: bonus2_http.zig
const std = @import("std");
const c = @cImport({
@cInclude("curl/curl.h");
});
pub const HttpError = error{
InitFailed,
SetOptionFailed,
PerformFailed,
};
const WriteCallback = fn (
ptr: [*]u8,
size: usize,
nmemb: usize,
userdata: ?*anyopaque,
) callconv(.C) usize;
fn writeCallback(
ptr: [*]u8,
size: usize,
nmemb: usize,
userdata: ?*anyopaque,
) callconv(.C) usize {
const real_size = size * nmemb;
const buffer = @as(*std.ArrayList(u8), @ptrCast(@alignCast(userdata)));
buffer.appendSlice(ptr[0..real_size]) catch return 0;
return real_size;
}
pub const HttpClient = struct {
handle: ?*c.CURL,
pub fn init() HttpError!HttpClient {
// TODO: 実装
}
pub fn deinit(self: *HttpClient) void {
// TODO: 実装
}
pub fn get(
self: HttpClient,
allocator: std.mem.Allocator,
url: []const u8,
) HttpError![]u8 {
// TODO: 実装
// コールバックを使用してレスポンスを取得
}
pub fn post(
self: HttpClient,
allocator: std.mem.Allocator,
url: []const u8,
data: []const u8,
) HttpError![]u8 {
// TODO: 実装
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
var client = try HttpClient.init();
defer client.deinit();
// GETリクエスト
const response = try client.get(allocator, "https://httpbin.org/get");
defer allocator.free(response);
std.debug.print("Response:\n{s}\n", .{response});
}
要件:
- GETリクエスト
- POSTリクエスト
- レスポンスボディの取得
- エラーハンドリング
- マンダトリー: 64点以上で合格(80%)
- ボーナス: 追加評価
- すべてのコードを
exercise15/ディレクトリに配置 - 以下のファイル構成にすること:
評価基準
| 項目 | 配点 |
|---|---|
| Part 1: Cヘッダーのインポート | 20点 |
| Part 2: POSIX APIの活用 | 20点 |
| Part 3: C互換関数のエクスポート | 20点 |
| Part 4: Cライブラリのラッピング | 20点 |
| **マンダトリー合計** | **80点** |
| Bonus 1: SQLiteラッパー | 10点 |
| Bonus 2: HTTPクライアント | 10点 |
| **ボーナス合計** | **20点** |
合格基準
提出方法
exercise15/
├── part1_import.zig
├── part2_posix.zig
├── part3_export.zig
├── mathlib.h
├── part4_wrapper.zig
├── bonus1_sqlite.zig # ボーナス
├── bonus2_http.zig # ボーナス
└── build.zig # ビルド設定
# Part 1
zig build-exe part1_import.zig -lc
./part1_import
# Part 2
zig build-exe part2_posix.zig -lc
./part2_posix
# Part 3
zig build-exe part3_export.zig -lc
./part3_export
# Part 4
zig build-exe part4_wrapper.zig -lc
./part4_wrapper
# Bonus 1 (SQLite必要)
zig build-exe bonus1_sqlite.zig -lc -lsqlite3
./bonus1_sqlite
# Bonus 2 (libcurl必要)
zig build-exe bonus2_http.zig -lc -lcurl
./bonus2_http
ヒント
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});
- null終端文字列の作成:
const path_z = try std.heap.page_allocator.dupeZ(u8, path);
defer std.heap.page_allocator.free(path_z);
- C言語のポインタ:
const c_ptr: [*c]u8 = c.malloc(size);
// [*c] は C互換のポインタ型
- エラーコードのチェック:
if (result < 0) {
return error.Failed;
}
- 構造体の初期化:
var tv: c.struct_timeval = undefined;
_ = c.gettimeofday(&tv, null);
参考資料
- Zig C Interop: https://ziglang.org/documentation/master/#C
- SQLite C API: https://www.sqlite.org/capi3ref.html
- libcurl: https://curl.se/libcurl/c/
- POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/