{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/cpp-programming-pitfalls/",
  "atlas": "https://www.pystone.net/?node=cpp-programming-pitfalls#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/cpp-programming-pitfalls.md",
  "context": "https://www.pystone.net/notes/cpp-programming-pitfalls.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:cpp-programming-pitfalls",
  "slug": "cpp-programming-pitfalls",
  "title": "C++ 编程注意事项",
  "type": "note",
  "visibility": "public",
  "idStability": "rename-stable",
  "author": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "publisher": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "aliases": [],
  "summary": "C++ 编程注意事项 需要长期存在的数据应该声明为静态或全局变量。 函数内声明的局部变量在函数返回后会被销毁。如果这些变量需要在函数返回后继续使用，会导致未定义行为。 对于需要持续使用的结构体变量，应该将其声明为静态或全局变量，确保其生命周期覆盖整个程序运行时间。",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "d4a6ef3cbb3fbd09ae62e6ba8d153ab7e999addab22b7d61e6a2742ace89af3c",
  "assets": [],
  "headings": [
    {
      "depth": 1,
      "text": "C++ 编程注意事项",
      "anchor": "c-编程注意事项",
      "citation": "https://www.pystone.net/notes/cpp-programming-pitfalls/#c-%E7%BC%96%E7%A8%8B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9"
    },
    {
      "depth": 2,
      "text": "malloc和calloc",
      "anchor": "malloc和calloc",
      "citation": "https://www.pystone.net/notes/cpp-programming-pitfalls/#malloc%E5%92%8Ccalloc"
    },
    {
      "depth": 2,
      "text": "malloc",
      "anchor": "malloc",
      "citation": "https://www.pystone.net/notes/cpp-programming-pitfalls/#malloc"
    },
    {
      "depth": 2,
      "text": "calloc",
      "anchor": "calloc",
      "citation": "https://www.pystone.net/notes/cpp-programming-pitfalls/#calloc"
    }
  ],
  "claims": [],
  "outgoing": [],
  "incoming": [
    {
      "id": "note:programming-languages-and-runtimes",
      "title": "编程语言与运行时",
      "url": "https://www.pystone.net/notes/programming-languages-and-runtimes/",
      "atlas": "https://www.pystone.net/?node=programming-languages-and-runtimes#knowledge-atlas",
      "label": "编程语言与运行时",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "本层中的“C++ 编程注意事项”导航项",
      "citation": "https://www.pystone.net/notes/programming-languages-and-runtimes/#%E6%9C%AC%E5%B1%82"
    }
  ],
  "contentMarkdown": "# C++ 编程注意事项\n需要长期存在的数据应该声明为静态或全局变量。\n- 函数内声明的局部变量在函数返回后会被销毁。如果这些变量需要在函数返回后继续使用，会导致未定义行为。\n- 对于需要持续使用的结构体变量，应该将其声明为静态或全局变量，确保其生命周期覆盖整个程序运行时间。\n\n```cpp\nvoid installSignalHandlers() {\n    struct sigaction s_action;\n    s_action.sa_sigaction = signalHandler;\n    s_action.sa_flags = SA_RESTART | SA_SIGINFO;\n    sigemptyset(&s_action.sa_mask);\n\n    for (int i = 0; i < kNumHandledSignals; ++i) {\n        sigaddset(&s_action.sa_mask, kExceptionSignals[i]);\n    }\n\n    for (int i = 0; i < kNumHandledSignals; i++) {\n        if (sigaction(kExceptionSignals[i], &s_action, &oldHandlers[kExceptionSignals[i]]) != 0) {\n            LOGE(\"Failed to install signal handler for signal %d\", kExceptionSignals[i]);\n        } else {\n            LOGD(\"Successfully installed signal handler for signal %d\", kExceptionSignals[i]);\n        }\n    }\n}\n\n```\n\n当函数返回时，`s_action` 变量被销毁，后续信号处理可能会访问已经无效的内存。\n\n## malloc和calloc\n\n## malloc\n\n`malloc` 函数用于分配指定字节数的内存，但不会对分配的内存进行初始化。\n\n```cpp\nvoid* malloc(size_t size);\n```\n- **效率高**：由于不进行初始化操作，`malloc` 通常比 `calloc` 快。\n\n为字符串分配内存后调用 `memset` 将内存初始化为零是一个良好的编程习惯，但并不是严格必要的.\n\n```cpp\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nint main() {\n    size_t size = 100;\n    char *str = (char *)malloc(size);\n    if (str == NULL) {\n        perror(\"malloc failed\");\n        return 1;\n    }\n    memset(str, 0, size);\n\n    snprintf(str, size, \"Hello, world!\");\n    printf(\"%s\\n\", str);\n\n    free(str);\n    return 0;\n}\n\n```\n\n如果不手动进行初始化, 并且在使用时未完全覆盖分配的内存，可能会留下未初始化的字节。这些字节的内容是不确定的，可能导致未定义的行为或垃圾数据。\n\n字符串操作（例如 `strlen`, `strcat`, `strcmp`）依赖于零终止符。如果字符串没有正确终止，可能会导致函数读取超出预期范围的内存，导致未定义行为或者崩溃。\n\n- 如果你分配内存后立即将其完全覆盖（例如，通过 `strcpy` 或者 `snprintf`），则不需要初始化内存为零。\n- 如果你只是部分覆盖内存，并且依赖于剩余部分是零，那么你需要初始化内存为零。\n\n手动初始化为0的情况:\n```cpp\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nint main() {\n    size_t size = 100;\n    char *str = (char *)malloc(size);\n    if (str == NULL) {\n        perror(\"malloc failed\");\n        return 1;\n    }\n    memset(str, 0, size);\n\n    snprintf(str, size, \"Hello, world!\");\n    printf(\"%s\\n\", str);\n\n    free(str);\n    return 0;\n}\n\n```\n\n不手动初始化, 但是安全的情况: `snprintf` 直接将字符串写入分配的内存块。由于 `snprintf` 完全覆盖了内存并且自动添加了字符串的零终止符，因此字符串操作是安全的。\n```cpp\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nint main() {\n    size_t size = 100;\n    char *str = (char *)malloc(size);\n    if (str == NULL) {\n        perror(\"malloc failed\");\n        return 1;\n    }\n\n    snprintf(str, size, \"Hello, world!\");\n    printf(\"%s\\n\", str);\n\n    free(str);\n    return 0;\n}\n\n```\n\n内存未完全覆盖导致异常的情况:  字符串没有写入`\\0` 终止符, 打印时可能会读取到未初始化的内存内容.\n```cpp\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nint main() {\n    size_t size = 100;\n    char *str = (char *)malloc(size);\n    if (str == NULL) {\n        perror(\"malloc failed\");\n        return 1;\n    }\n\n    strcpy(str, \"Hello\");  // 只写入 \"Hello\"\n    printf(\"%s\\n\", str);   // 打印时可能会读取到未初始化的内存内容\n\n    free(str);\n    return 0;\n}\n\n```\n\n## calloc\n\n`calloc` 函数用于分配内存，并将分配的内存块初始化为零。\n```cpp\nvoid* calloc(size_t num, size_t size);\n```\n\n- `num`：要分配的元素个数。\n- `size`：每个元素的大小（以字节为单位）。\n\n`calloc` 通常用于分配和初始化数组，其参数是元素个数和每个元素的大小。\n\n```cpp\nchar *path = (char *) calloc(10, PATH_MAX);\n```\n\n`calloc` 将分配 `10 * PATH_MAX` 字节的内存，并将这段内存初始化为零。\n\n```cpp\n// 使用示例：分配一个可以存储10个字符串的内存块，每个字符串的最大长度为 PATH_MAX\nchar *pathArray = (char *) calloc(10, PATH_MAX);\nif (pathArray == NULL) {\n    // 内存分配失败，进行错误处理\n}\n\n// 使用 pathArray 处理10个字符串\nfor (int i = 0; i < 10; i++) {\n    snprintf(&pathArray[i * PATH_MAX], PATH_MAX, \"String %d\", i);\n    printf(\"String %d: %s\\n\", i, &pathArray[i * PATH_MAX]);\n}\n\n// 释放内存\nfree(pathArray);\n```\n`pathArray` 被用作一个二维字符数组，能够存储 10 个字符串，每个字符串的最大长度为 `PATH_MAX`。\n"
}
