評価15: C言語連携 - 評価基準
評価の目的
この評価では、ZigとC言語の相互運用能力を確認します。Cヘッダーのインポート、POSIX APIの活用、C互換関数のエクスポート、Cライブラリのラッピングなど、実践的なC連携スキルを評価します。
学習目標
この評価を通じて、以下の能力を確認します:
- @cImport を使ったCヘッダーのインポート
- C言語の関数とデータ型の適切な使用
- export キーワードによるC互換関数の作成
- Cライブラリの型安全なラッピング
- メモリ管理とリソース解放の適切な処理
評価項目
1. Cヘッダーのインポート(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| ヘッダーのインポート | 5点 | @cImport と @cInclude の正しい使用 |
| C関数の呼び出し | 5点 | printf、sqrt、strlenなどが正しく動作 |
| メモリ管理 | 5点 | malloc/freeの適切な使用とリーク防止 |
| 文字列の扱い | 5点 | null終端文字列の正しい処理 |
確認ポイント:
- [ ] @cImport ブロックが正しく記述されている
- [ ] C関数が正常に呼び出される
- [ ] メモリリークがない
- [ ] null終端文字列を適切に作成・変換
良い実装例:
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
@cInclude("string.h");
@cInclude("math.h");
});
pub fn printMessage(message: []const u8) void {
const c_str = std.heap.page_allocator.dupeZ(u8, message) catch return;
defer std.heap.page_allocator.free(c_str);
_ = c.printf("%s\n", c_str.ptr);
}
pub fn calculateSqrt(x: f64) f64 {
return c.sqrt(x);
}
pub fn duplicateData(data: []const u8) ?[*]u8 {
const ptr = c.malloc(data.len + 1);
if (ptr == null) return null;
const result: [*]u8 = @ptrCast(ptr);
@memcpy(result[0..data.len], data);
result[data.len] = 0; // null終端
return result;
}
// 悪い例:メモリリーク
pub fn duplicateData(data: []const u8) ?[*]u8 {
const ptr = c.malloc(data.len); // +1 がない
// コピー処理なし
return @ptrCast(ptr); // リークの原因
}
2. POSIX APIの活用(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| ファイル作成・書き込み | 6点 | open、write、closeが正しく動作 |
| ファイル読み込み | 6点 | read操作が適切に実装されている |
| ファイル情報取得 | 4点 | statでファイルサイズを取得できる |
| エラーハンドリング | 4点 | POSIXエラーを適切に処理 |
確認ポイント:
- [ ] ファイルディスクリプタが正しく管理されている
- [ ] エラーコードを適切に処理
- [ ] リソースリークがない
- [ ] パーミッションが適切に設定されている
POSIX API使用例:
const c = @cImport({
@cInclude("fcntl.h");
@cInclude("unistd.h");
@cInclude("sys/stat.h");
});
pub fn writeFile(path: []const u8, data: []const u8) !void {
const path_z = try std.heap.page_allocator.dupeZ(u8, path);
defer std.heap.page_allocator.free(path_z);
const fd = c.open(
path_z.ptr,
c.O_WRONLY | c.O_CREAT | c.O_TRUNC,
0o644
);
if (fd < 0) return error.OpenFailed;
defer _ = c.close(fd);
const written = c.write(fd, data.ptr, data.len);
if (written < 0) return error.WriteFailed;
}
pub fn getFileSize(path: []const u8) !usize {
const path_z = try std.heap.page_allocator.dupeZ(u8, path);
defer std.heap.page_allocator.free(path_z);
var st: c.struct_stat = undefined;
if (c.stat(path_z.ptr, &st) < 0) {
return error.GetSizeFailed;
}
return @intCast(st.st_size);
}
3. C互換関数のエクスポート(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| export関数の実装 | 6点 | gcd、lcm、factorialが正しく実装 |
| C互換型の使用 | 6点 | c_int、c_longlongを適切に使用 |
| ヘッダーファイル | 4点 | 対応するCヘッダーが正しく記述されている |
| アルゴリズムの正確性 | 4点 | 数学的に正しい計算結果 |
確認ポイント:
- [ ] export キーワードが使用されている
- [ ] C互換の型を使用している
- [ ] 関数シグネチャがヘッダーと一致
- [ ] Cから呼び出し可能
エクスポート関数の例:
// ユークリッドの互除法
export fn gcd(a: c_int, b: c_int) c_int {
var x = a;
var y = b;
while (y != 0) {
const temp = y;
y = @rem(x, y);
x = temp;
}
return if (x < 0) -x else x;
}
export fn lcm(a: c_int, b: c_int) c_int {
if (a == 0 or b == 0) return 0;
return @divTrunc((a * b), gcd(a, b));
}
export fn factorial(n: c_int) c_longlong {
if (n <= 0) return 1;
var result: c_longlong = 1;
var i: c_int = 2;
while (i <= n) : (i += 1) {
result *= i;
}
return result;
}
export fn sumArray(arr: [*]const c_int, len: c_int) c_int {
var sum: c_int = 0;
var i: c_int = 0;
while (i < len) : (i += 1) {
sum += arr[@intCast(i)];
}
return sum;
}
対応するヘッダーファイル:
#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
4. Cライブラリのラッピング(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| DateTime構造体 | 6点 | 時刻情報を適切にラップ |
| 現在時刻取得 | 5点 | time、localtimeを正しく使用 |
| 時刻変換 | 5点 | Unix時刻との相互変換が正しい |
| フォーマット | 4点 | 人間が読める形式に変換 |
確認ポイント:
- [ ] C APIをZigの型システムでラップ
- [ ] エラーハンドリングが適切
- [ ] メモリ安全性が保たれている
- [ ] 使いやすいインターフェース
ラッパーの例:
const c = @cImport({
@cInclude("time.h");
});
pub const DateTime = struct {
year: u32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
pub fn now() !DateTime {
const t = c.time(null);
if (t == -1) return error.GetTimeFailed;
const tm_ptr = c.localtime(&t);
if (tm_ptr == null) return error.LocalTimeFailed;
const tm = tm_ptr.*;
return DateTime{
.year = @intCast(tm.tm_year + 1900),
.month = @intCast(tm.tm_mon + 1),
.day = @intCast(tm.tm_mday),
.hour = @intCast(tm.tm_hour),
.minute = @intCast(tm.tm_min),
.second = @intCast(tm.tm_sec),
};
}
pub fn format(
self: DateTime,
allocator: std.mem.Allocator,
) ![]u8 {
return try std.fmt.allocPrint(
allocator,
"{d:0>4}-{d:0>2}-{d:0>2} {d:0>2}:{d:0>2}:{d:0>2}",
.{
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
}
);
}
};
5. ボーナス課題(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| SQLiteラッパー | 10点 | Database、Statementクラスが動作 |
| HTTPクライアント | 10点 | libcurlを使ったGET/POSTリクエスト |
ボーナス評価ポイント:
- [ ] SQLiteの準備、実行、結果取得が正しい
- [ ] HTTPリクエストとレスポンスの処理が適切
- [ ] エラーハンドリングが完璧
- [ ] 実用的なAPI設計
チェックリスト
実装前の確認
- [ ] 必要なCヘッダーを特定している
- [ ] C APIのドキュメントを読んでいる
- [ ] メモリ管理の責任を理解している
- [ ] エラーコードの意味を把握している
実装後の確認
- [ ] すべてのプログラムが正しくコンパイル
- [ ] C関数の呼び出しが成功
- [ ] メモリリークがない
- [ ] エラーケースが適切に処理されている
コード品質の確認
- [ ] null終端文字列の処理が正確
- [ ] リソースが確実に解放されている
- [ ] 型変換が安全に行われている
- [ ] Cコードとの互換性が保たれている
合格基準
| レベル | 点数 | 説明 |
|---|---|---|
| 優秀 | 90-100 | すべての機能を完璧に実装、ボーナス課題も完了 |
| 良好 | 80-89 | マンダトリー課題を完全実装、ボーナス課題の一部完了 |
| 合格 | 64-79 | マンダトリー課題の80%以上を正しく実装 |
| 要再提出 | 0-63 | C連携が動作しないまたはメモリリークあり |
最低合格ライン: 64点以上(マンダトリー80点満点の80%)
よくある減点ポイント
1. null終端文字列の処理ミス(-5〜10点)
// NG: null終端なし
pub fn duplicateData(data: []const u8) ?[*]u8 {
const ptr = c.malloc(data.len);
@memcpy(@as([*]u8, @ptrCast(ptr))[0..data.len], data);
return @ptrCast(ptr); // null終端がない
}
// OK: null終端あり
pub fn duplicateData(data: []const u8) ?[*]u8 {
const ptr = c.malloc(data.len + 1); // +1 が重要
const result: [*]u8 = @ptrCast(ptr);
@memcpy(result[0..data.len], data);
result[data.len] = 0; // null終端
return result;
}
2. リソースリーク(-10〜20点)
// NG: ファイルディスクリプタのリーク
pub fn writeFile(path: []const u8, data: []const u8) !void {
const fd = c.open(path, c.O_WRONLY | c.O_CREAT);
_ = c.write(fd, data.ptr, data.len);
// close を忘れている!
}
// OK: 確実にクローズ
pub fn writeFile(path: []const u8, data: []const u8) !void {
const fd = c.open(path, c.O_WRONLY | c.O_CREAT, 0o644);
if (fd < 0) return error.OpenFailed;
defer _ = c.close(fd); // 確実にクローズ
const written = c.write(fd, data.ptr, data.len);
if (written < 0) return error.WriteFailed;
}
3. 型変換の誤り(-5点)
// NG: 危険な型変換
const result: [*]u8 = ptr; // 型チェックなし
// OK: 明示的な型変換
const result: [*]u8 = @ptrCast(@alignCast(ptr));
学習の手引き
C連携の基礎
- メモリ管理
- エラーハンドリング
効果的な学習方法
- 小さいプログラムから
- Cドキュメントの活用
- デバッグツールの使用
ピアレビューのポイント
評価者は以下の点を確認してください:
1. ビルドと実行
# 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
2. コード確認
- C連携の正確性
- メモリリークの有無
- エラーハンドリングの適切性
- ヘッダーファイルとの整合性
3. 理解度の確認
質問例:- 「null終端文字列はなぜ必要ですか?」
- 「malloc と Zigのアロケータの違いは?」
- 「POSIXエラーはどう処理しますか?」
- Zig C Interop: https://ziglang.org/documentation/master/#C
- POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/
- SQLite C API: https://www.sqlite.org/capi3ref.html
- libcurl: https://curl.se/libcurl/c/
フィードバックの例
良いフィードバック
> 「C言語連携の実装が非常に優れています。@cImport の使用が正しく、C関数の呼び出しもエラーなく動作しています。null終端文字列の処理も適切で、メモリリークもありません。POSIX APIの実装では、ファイルディスクリプタの管理が完璧で、deferを使った確実なリソース解放が実現できています。export関数のヘッダーファイルも整合性が取れており、実用的です。」
改善が必要な場合のフィードバック
> 「基本的なC連携は動作していますが、duplicateData関数でnull終端文字列の処理が抜けています。data.len + 1 でメモリを確保し、最後にnull文字を追加してください。また、POSIX APIのwriteFile関数でcloseを忘れており、ファイルディスクリプタがリークしています。deferを使って確実にクローズするように修正してください。」
参考資料
まとめ
ZigとC言語の相互運用は、既存のエコシステムを活用する上で重要です。この評価を通じて、Cヘッダーのインポート、POSIXAPIの使用、C互換関数のエクスポート、Cライブラリのラッピングなど、実践的なC連携スキルを習得しました。これらの技術により、Zigで既存のCライブラリを活用し、またZigのコードをC言語から呼び出すことが可能になります。