网罗开发 (小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


背景与问题描述

cJSON.h 里你可能看到类似这样的宏定义(这是为了描述“类型索引”):

#define cJSON_False     0
#define cJSON_True      1
#define cJSON_NULL      2
#define cJSON_Number    3
#define cJSON_String    4
#define cJSON_Array     5
#define cJSON_Object    6

#define cJSON_IsReference 256

你运行程序时,打印 item->type 却看到这样的数值:

  • cJSON_String 打印成 16

  • cJSON_Number 打印成 8

  • 甚至还有 256 之类的值(IsReference

看起来“头文件里定义的数字”和运行时的 type 值“对不上”。所以你问:为什么会变成 816 等?是不是经过了左移(bit shift)操作?

原因(核心)—— 类型是用“位标志(bit flags)”来存储的

关键点:头文件里给出的那组数字(0、1、2、3、4……)是“类型索引/编号”,而不是 item->type 在运行时的存储值。

cJSON 在内部把类型表示成“位掩码(bit mask)/标志(flags)”。也就是说在运行时它把“类型编号”转换为 1 << typeIndex 这样一个位值来存储:

  • cJSON_False 索引 0 → 存储位值 1 << 0 = 1

  • cJSON_True 索引 1 → 存储位值 1 << 1 = 2

  • cJSON_NULL 索引 2 → 存储位值 1 << 2 = 4

  • cJSON_Number索引 3 → 存储位值 1 << 3 = 8

  • cJSON_String索引 4 → 存储位值 1 << 4 = 16

  • ...

还有一些额外的标志,比如 cJSON_IsReference 在头文件定义为 256(等于 1 << 8),这是为了把“引用”当作额外的位来表示(与类型位可以共存)。

所以,当你直接 printf("%d\n", item->type),你看到的是“位掩码”的值(如 816),而不是头文件那组“索引式”的数字(3、4)。

推荐的使用方式(三种稳妥做法)

  1. 优先使用 cJSON 提供的类型检查函数/宏(如果可用),例如 cJSON_IsString(item)cJSON_IsNumber(item),这样代码更清晰,不依赖内部实现细节(也更兼容未来 cJSON 的变动)。

  2. 如果要自己检查位值,使用位运算并加上移位(shift)逻辑,例如:

    if (item->type & (1 << cJSON_String)) { /* String */ }
    if (item->type & (1 << cJSON_Number)) { /* Number */ }
    
    
  3. 或者把 item->type 的位值转换成“可读名称”(写一个映射函数),这样打印出的结果会直观很多。

可运行 Demo:演示类型索引与位掩码的关系,并给出映射函数

下面是一个完整的 C Demo。说明:

  • 假设你把 cJSON.c / cJSON.h 放在同一目录下(或已安装 cJSON 库)。

  • 源文件名 demo.c

  • 编译方式在说明里给出(gcc 方式)。

注意:如果你没有 cJSON 源文件,可以从官方仓库克隆(https://github.com/DaveGamble/cJSON),把 cJSON.hcJSON.c 放到 demo 同目录即可。

demo.c

/*
 * demo.c
 * 说明:
 *   - 演示 cJSON item->type 在运行时是位掩码(1 << typeIndex)
 *   - 提供一个把 typeFlag 转换为可读字符串的函数
 *
 * 编译:gcc demo.c cJSON.c -o demo
 * 运行:./demo
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"

/* 用于把位掩码转换成人可读的类型名(可能有多个标志并存) */
const char* cJSON_GetTypeNames(int typeFlag) {
    static char buf[256];
    buf[0] = '\0';

    const char *names[] = {
        "False",   // index 0
        "True",    // index 1
        "Null",    // index 2
        "Number",  // index 3
        "String",  // index 4
        "Array",   // index 5
        "Object"   // index 6
    };
    int first = 1;
    for (int i = 0; i <= 6; ++i) {
        int mask = (1 << i); // runtime中的实际位掩码
        if (typeFlag & mask) {
            if (!first) strcat(buf, "|");
            strcat(buf, names[i]);
            first = 0;
        }
    }
    // 检查 cJSON_IsReference(如果存在)
#ifdef cJSON_IsReference
    if (typeFlag & cJSON_IsReference) {
        if (!first) strcat(buf, "|");
        strcat(buf, "IsReference");
        first = 0;
    }
#else
    /* 头文件通常定义 cJSON_IsReference 为 256 (1<<8) */
    if (typeFlag & (1 << 8)) {
        if (!first) strcat(buf, "|");
        strcat(buf, "IsReference");
        first = 0;
    }
#endif

    if (buf[0] == '\0') return "Unknown/None";
    return buf;
}

/* 辅助:打印单个 item 的信息 */
void print_item_info(const cJSON *item) {
    if (!item) return;
    printf("key = \"%s\"\n", item->string ? item->string : "(root child)");
    printf("  raw item->type = %d\n", item->type);
    printf("  type bits (hex) = 0x%X\n", item->type);
    printf("  readable types = %s\n", cJSON_GetTypeNames(item->type));

    /* 演示如何使用位移检测(推荐) */
    if (item->type & (1 << cJSON_String)) {
        printf("  -> Detected via (item->type & (1<<cJSON_String)): STRING\n");
    }
    if (item->type & (1 << cJSON_Number)) {
        printf("  -> Detected via (item->type & (1<<cJSON_Number)): NUMBER\n");
    }
    if (item->type & (1 << cJSON_Array)) {
        printf("  -> Detected via (item->type & (1<<cJSON_Array)): ARRAY\n");
    }
    if (item->type & (1 << cJSON_Object)) {
        printf("  -> Detected via (item->type & (1<<cJSON_Object)): OBJECT\n");
    }

    /* 如果 cJSON 提供了检查宏,优先用宏(兼容性更好) */
#ifdef cJSON_IsString
    if (cJSON_IsString((cJSON*)item)) {
        printf("  -> cJSON_IsString macro says: STRING\n");
    }
#endif
#ifdef cJSON_IsNumber
    if (cJSON_IsNumber((cJSON*)item)) {
        printf("  -> cJSON_IsNumber macro says: NUMBER\n");
    }
#endif

    printf("\n");
}

int main(void) {
    /* 测试 JSON */
    const char *json = "{"
        "\"name\":\"Alice\","
        "\"age\":30,"
        "\"active\":true,"
        "\"items\": [1, 2, 3],"
        "\"obj\": {\"k\":\"v\"},"
        "\"nullval\": null"
    "}";

    cJSON *root = cJSON_Parse(json);
    if (!root) {
        printf("parse error\n");
        return 1;
    }

    printf("Parsed JSON. Iterate top-level children:\n\n");
    for (cJSON *item = root->child; item != NULL; item = item->next) {
        print_item_info(item);
    }

    /* 额外演示:某些内部函数/字段 */
    printf("Note: cJSON header has defines like cJSON_String=%d, cJSON_Number=%d\n", cJSON_String, cJSON_Number);
    printf("But runtime item->type stores the bitmask (1<<typeIndex)\n");
    printf("So cJSON_String index=%d corresponds to runtime mask=1<<%d = %d\n\n", cJSON_String, cJSON_String, (1 << cJSON_String));

    cJSON_Delete(root);
    return 0;
}

编译与运行(步骤)

假设你已经把 demo.ccJSON.ccJSON.h 放在同一目录下:

gcc demo.c cJSON.c -o demo
./demo

预期输出(示例)

运行后你会看到类似这样的输出(注:数字可能随 cJSON 版本不同略有格式差异,但思路一致):

Parsed JSON. Iterate top-level children:

key = "name"
  raw item->type = 16
  type bits (hex) = 0x10
  readable types = String
  -> Detected via (item->type & (1<<cJSON_String)): STRING
  -> cJSON_IsString macro says: STRING

key = "age"
  raw item->type = 8
  type bits (hex) = 0x8
  readable types = Number
  -> Detected via (item->type & (1<<cJSON_Number)): NUMBER
  -> cJSON_IsNumber macro says: NUMBER

key = "active"
  raw item->type = 2
  type bits (hex) = 0x2
  readable types = True
  ...

key = "items"
  raw item->type = 32
  type bits (hex) = 0x20
  readable types = Array

key = "obj"
  raw item->type = 64
  type bits (hex) = 0x40
  readable types = Object

key = "nullval"
  raw item->type = 4
  type bits (hex) = 0x4
  readable types = Null

Note: cJSON header has defines like cJSON_String=4, cJSON_Number=3
But runtime item->type stores the bitmask (1<<typeIndex)
So cJSON_String index=4 corresponds to runtime mask=1<<4 = 16

你可以看到 raw item->type168 等(即 1<<41<<3),而 cJSON_String 宏本身的数值仍是 4——两者不是同一个“语义”。这就是你遇到的“数值不一致”的根本原因。

进一步的建议与实践要点

  • 别直接比较 item->type == cJSON_String:这样会失败,因为 item->type 是位掩码(16),cJSON_String是索引(4)。

  • 优选使用库提供的宏/函数:如果 cJSON_IsString / cJSON_IsNumber 等宏存在,就直接用它们,语义最清晰,也最兼容不同 cJSON 版本。

  • 如果自己判断,记住左移:用 (item->type & (1 << cJSON_String)) 来检测。

  • 映射函数便于调试:调试时把 item->type 显示成可读的名字,能快速定位问题。

  • 注意复合标志:一个 item 可能同时带有 IsReference,因此一定用位检查而不是等于比较。

  • 如果你维护一个库,写封装函数:把类型判断封装成函数,避免到处重复写位运算。

总结

头文件里看到的 cJSON_String = 4 是“类型的索引”,而 item->type 在运行时是以“位掩码(1 << index)”的形式存储的;因此直接打印 item->type 会得到 1<<4 = 16 这样的数值。正确做法是使用 cJSON 提供的 cJSON_IsXxx() 宏或自己用 (item->type & (1 << cJSON_String)) 的位运算来判断类型,或者把位掩码转成可读字符串输出以便调试。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐