返回「计算机、信息技术与工程」

C++ 编程注意事项

C++ 编程注意事项 需要长期存在的数据应该声明为静态或全局变量。 函数内声明的局部变量在函数返回后会被销毁。如果这些变量需要在函数返回后继续使用,会导致未定义行为。 对于需要持续使用的结构体变量,应该将其声明为静态或全局变量,确保其生命周期覆盖整个程序运行时间。

更多
Markdown 结构化数据
本文目录 3 个章节

C++ 编程注意事项

需要长期存在的数据应该声明为静态或全局变量。

  • 函数内声明的局部变量在函数返回后会被销毁。如果这些变量需要在函数返回后继续使用,会导致未定义行为。
  • 对于需要持续使用的结构体变量,应该将其声明为静态或全局变量,确保其生命周期覆盖整个程序运行时间。
void installSignalHandlers() {
    struct sigaction s_action;
    s_action.sa_sigaction = signalHandler;
    s_action.sa_flags = SA_RESTART | SA_SIGINFO;
    sigemptyset(&s_action.sa_mask);

    for (int i = 0; i < kNumHandledSignals; ++i) {
        sigaddset(&s_action.sa_mask, kExceptionSignals[i]);
    }

    for (int i = 0; i < kNumHandledSignals; i++) {
        if (sigaction(kExceptionSignals[i], &s_action, &oldHandlers[kExceptionSignals[i]]) != 0) {
            LOGE("Failed to install signal handler for signal %d", kExceptionSignals[i]);
        } else {
            LOGD("Successfully installed signal handler for signal %d", kExceptionSignals[i]);
        }
    }
}

当函数返回时,s_action 变量被销毁,后续信号处理可能会访问已经无效的内存。

malloc和calloc

malloc

malloc 函数用于分配指定字节数的内存,但不会对分配的内存进行初始化。

void* malloc(size_t size);
  • 效率高:由于不进行初始化操作,malloc 通常比 calloc 快。

为字符串分配内存后调用 memset 将内存初始化为零是一个良好的编程习惯,但并不是严格必要的.

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

int main() {
    size_t size = 100;
    char *str = (char *)malloc(size);
    if (str == NULL) {
        perror("malloc failed");
        return 1;
    }
    memset(str, 0, size);

    snprintf(str, size, "Hello, world!");
    printf("%s\n", str);

    free(str);
    return 0;
}

如果不手动进行初始化, 并且在使用时未完全覆盖分配的内存,可能会留下未初始化的字节。这些字节的内容是不确定的,可能导致未定义的行为或垃圾数据。

字符串操作(例如 strlen, strcat, strcmp)依赖于零终止符。如果字符串没有正确终止,可能会导致函数读取超出预期范围的内存,导致未定义行为或者崩溃。

  • 如果你分配内存后立即将其完全覆盖(例如,通过 strcpy 或者 snprintf),则不需要初始化内存为零。
  • 如果你只是部分覆盖内存,并且依赖于剩余部分是零,那么你需要初始化内存为零。

手动初始化为0的情况:

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

int main() {
    size_t size = 100;
    char *str = (char *)malloc(size);
    if (str == NULL) {
        perror("malloc failed");
        return 1;
    }
    memset(str, 0, size);

    snprintf(str, size, "Hello, world!");
    printf("%s\n", str);

    free(str);
    return 0;
}

不手动初始化, 但是安全的情况: snprintf 直接将字符串写入分配的内存块。由于 snprintf 完全覆盖了内存并且自动添加了字符串的零终止符,因此字符串操作是安全的。

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

int main() {
    size_t size = 100;
    char *str = (char *)malloc(size);
    if (str == NULL) {
        perror("malloc failed");
        return 1;
    }

    snprintf(str, size, "Hello, world!");
    printf("%s\n", str);

    free(str);
    return 0;
}

内存未完全覆盖导致异常的情况: 字符串没有写入\0 终止符, 打印时可能会读取到未初始化的内存内容.

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

int main() {
    size_t size = 100;
    char *str = (char *)malloc(size);
    if (str == NULL) {
        perror("malloc failed");
        return 1;
    }

    strcpy(str, "Hello");  // 只写入 "Hello"
    printf("%s\n", str);   // 打印时可能会读取到未初始化的内存内容

    free(str);
    return 0;
}

calloc

calloc 函数用于分配内存,并将分配的内存块初始化为零。

void* calloc(size_t num, size_t size);
  • num:要分配的元素个数。
  • size:每个元素的大小(以字节为单位)。

calloc 通常用于分配和初始化数组,其参数是元素个数和每个元素的大小。

char *path = (char *) calloc(10, PATH_MAX);

calloc 将分配 10 * PATH_MAX 字节的内存,并将这段内存初始化为零。

// 使用示例:分配一个可以存储10个字符串的内存块,每个字符串的最大长度为 PATH_MAX
char *pathArray = (char *) calloc(10, PATH_MAX);
if (pathArray == NULL) {
    // 内存分配失败,进行错误处理
}

// 使用 pathArray 处理10个字符串
for (int i = 0; i < 10; i++) {
    snprintf(&pathArray[i * PATH_MAX], PATH_MAX, "String %d", i);
    printf("String %d: %s\n", i, &pathArray[i * PATH_MAX]);
}

// 释放内存
free(pathArray);

pathArray 被用作一个二维字符数组,能够存储 10 个字符串,每个字符串的最大长度为 PATH_MAX