位元詩人 如何在 C 語言建立具有可攜性的布林(Boolean)型態

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

本文說明 C 語言中與布林相關的常見議題,以及在不同語言標準與開發環境下的處理方式,並展示一個具有良好可攜性的布林型態實作。

常見情境

C89

早期的 C(例如 C89)並沒有正式定義布林型態。不過,C 語言本身具有一個簡單的「布林語境」(boolean context)。

在這個語境中:

  • 0
  • 0.0
  • '\0'(字串結尾)
  • NULL

會被視為 偽(false),而任何 非零值 則會被視為 真(true)

如果需要顯式的布林型態,可以使用類似以下的做法:

typedef unsigned char BOOL;
#define TRUE  1
#define FALSE 0

不過,在 C99 之後語言已正式引入布林型態,因此在現代專案中不建議直接加入這組 macro。

C99 及其之後

C99 開始,標準函式庫 stdbool.h 明確定義了:

  • 布林型態:bool
  • 真值:true
  • 偽值:false

程式設計者只需要:

#include <stdbool.h>

即可使用標準布林型態,而不需要自行宣告。

C++

C++ 本身也內建布林型態:

  • bool
  • true
  • false

在實務開發中,C 與 C++ 程式碼混合使用並不罕見,因此設計可攜的布林型態時通常需要考慮這個情境。

Objective-C

由於歷史原因,Objective-C 並沒有沿用 C99 的 stdbool.h,而是使用自己的布林定義:

  • BOOL
  • YES
  • NO

無論是 CocoaGNUstep,通常都建議將 Objective-C Runtime 視為一個相對獨立的運行環境,避免在 Objective-C 程式中混入過多純 C 的自訂型態。

不需要定義布林的情境

在許多情況下,C 程式其實不需要明確定義布林型態

原因是 C 語言的條件判斷本來就建立在「零與非零」的概念上:

  • 0NULL → 偽(false)
  • 非零值或非 NULL → 真(true)

這個行為在所有 C 標準中都是一致的。

以下是一段示意程式碼:

list_t *lt = list_new();

if (!lt) {
    goto PROGRAM_ERROR;
}

/* Run some code here. */

list_delete((void *) lt);

return EXIT_SUCCESS;

PROGRAM_ERROR:

if (lt) {
    list_delete((void *) lt);
}

return EXIT_FAILURE;

在這段程式中,如果 list_new() 初始化失敗並回傳 NULL,程式就會跳到 PROGRAM_ERROR 標籤處理錯誤流程,最後回傳 EXIT_FAILURE

整個流程中並沒有使用顯式的布林型態,但程式依然能清楚地表達邏輯,因此在這類情境下 定義布林型態並不是必要條件

為什麼不直接使用 stdbool.h

在大多數現代 C 專案中,直接使用 stdbool.h 是最簡單且標準的做法。

然而,在某些情況下仍可能需要自訂布林型態,例如:

  • 專案需要支援 C89 或更舊的編譯器
  • 程式碼需要在 C 與 C++ 之間共享
  • 希望維持與 既有 API(例如 Win32 的 BOOL 一致的命名風格
  • 函式庫希望提供 自包含(self-contained)的型態定義

在這些情境下,提供一個可攜的布林型態標頭檔,能讓程式碼在不同平台與語言標準下保持一致。

跨平台布林宣告

由於不同 C 版本對布林的支援程度不同,綜合上述情境,我們可以設計一個具有良好可攜性的布林標頭檔:

/** @file       boolean.h
 *  @brief      Custom Boolean type for C (portable & self-contained)
 *  @author     ByteBard
 *  @copyright  MIT
 *
 *  Note: Win32 API provides its own BOOL type. This header is not intended for use in Win32 API programming.
 */

#ifndef CLIBS_BOOLEAN_H
#define CLIBS_BOOLEAN_H

#if defined(_WIN32)
    /* On Windows, use the native Win32 BOOL */
    #include <windows.h>

#else  /* Not Windows */

    #ifndef CLIBS_BOOL_DEFINED

        #if defined(__cplusplus)
            typedef bool BOOL;
            #define TRUE  true
            #define FALSE false

        #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
            #include <stdbool.h>
            typedef bool BOOL;
            #define TRUE  true
            #define FALSE false

        #else
            typedef unsigned char BOOL;
            #define TRUE  1
            #define FALSE 0
        #endif

        #define CLIBS_BOOL_DEFINED
    #endif  /* CLIBS_BOOL_DEFINED */

#endif  /* _WIN32 */

#endif  /* CLIBS_BOOLEAN_H */

這個實作考慮了以下幾種情境:

  • Windows 平台(直接使用 Win32 的 BOOL
  • C++
  • C99 以上版本
  • 舊版 C(例如 C89)

藉由條件編譯,可以讓同一份程式碼在不同平台與編譯器下都能正常運作。

Objective-C 的情境

Objective-C 通常會將 Foundation 視為預設環境,因此自訂布林型態的需求較低。

在這種情況下,本標頭檔通常會顯得多餘,因此本文不特別針對 Objective-C 進行處理。

另見

本標頭檔出自於專案:clibs

If you need a minimal portable boolean implementation for C projects,
you can directly include the header from the clibs repository.

關於作者

位元詩人 (ByteBard) 是資訊領域碩士,喜歡用開源技術來解決各式各樣的問題。這類技術跨平台、重用性高、技術生命長。

除了開源技術以外,位元詩人喜歡日本料理和黑咖啡,會一些日文,有時會自助旅行。