在编写C语言程序时何时需要进行函数参数合法性检查?(1)

引言

  我们在很多情况下需要用到assert,如当我们需要使用它去进行参数合法监测等,我们使用assert语句去实现,但是当软件发布时,因为性能问题等,会将assert去掉,所有的合法检测都失效了,我们又该如何监测参数的合法性呢?
  如果我们将函数加上检测参数合法性的代码,使用if语言去判断,去处理,这就无疑增加了程序的开销,在什么样的场景下我们需要检查,也是一个问题。
  下面我会分析我的解决方案。

如何进行参数检测?

程序处于开发阶段时

  在这个时期,我们可以使用assert去进行函数参数合法性检查,以便迅速的查找到函数传参数的错误,方便调试。

程序发布时

  在函数一般会通过一个宏取消assert,因为assert的输出信息没有意义了,而且没有assert可以提高效率。这时我们通常会使用if语句来判断是否合法,并进行不合法时的错误处理。
  其中glib的解决方案是,使用是如下的宏函数:

g_return_val_if_fail(expr, value)
g_return_if_fail(expr)
// example
g_async_queue_length_unlocked (GAsyncQueue* queue)
{
  g_return_val_if_fail (queue, 0);
  g_return_val_if_fail (g_atomic_int_get (&queue->ref_count) > 0, 0);

  return queue->queue->length - queue->waiting_threads;
}

  g_return_val_if_fail的描述是当表达式expr为true时,直接返回value,g_return_val_if_fail是一个宏函数,所以它可以返回任何类型的值,当然,它要和函数返回值类型一致。return_if_fail就是直接返回了,适用于没有返回值的函数。
  glib源代码中关于return_val_if_fail的实现,它比较复杂,但是主要就是当expr为true时,打印一条信息到某处,然后返回。我们可以简单的实现为如下的形式:

// user can disable parameters check by define NO_PARA_CHECK 
#ifndef NO_PARA_CHECK
#define return_val_if_fail(expr, ret)                      \ 
do {                                                       \
    if (!(expr)) {                                         \ 
        printf("%s:%d Warning: "#expr" failed.\n",         \
        __func__, __LINE__);                               \
		return (ret);                                      \
    }                                                      \
} while (0);
#define return_if_fail(expr)                               \ 
do {                                                       \
    if (!(expr)) {                                         \ 
        printf("%s:%d Warning: "#expr" failed.\n",         \
        __func__, __LINE__);                               \ 
		return;                                            \
    }                                                      \
} while (0);
#else  // ifndef NO_PARA_CHECK
#define return_val_if_fail(expr, ret)       
#define return_if_fail(expr)
#endif // ifndef NO_PARA_CHECK

  我们还可以利用宏来判断是否进行参数检测,正常运行的程序是不会出现参数不合法的情况,但是某些情况下,关于函数调用的参数是不太好预料的,使用参数检测能在错误的一开始就排查出来,能提高程序的可靠性。
  总结一下,assert和return_val_if_fail的区别在于,assert会因为参数不合法终止程序,而return_val_if_fail不会,它会返回,将错误交给上层调用者来处理。有时候我们并不希望一个应该非法的参数而结束程序,而希望系统继续执行。如编写测试程序时,我测试多个模块,我们不希望一个模块的错误,而停止测试其他模块。

何时进行参数检测?

  我们留到下篇分析。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以为您提供一份C语言代码实现通讯录,并且进行合法性检查的示例代码。请参考如下代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_NAME_LEN 20 #define MAX_PHONE_NUM_LEN 11 // 定义联系人结构体 typedef struct { char name[MAX_NAME_LEN]; char phone_num[MAX_PHONE_NUM_LEN]; } Contact; // 定义通讯录结构体 typedef struct { Contact* contacts; // 联系人数组 int count; // 联系人数量 int capacity; // 数组容量 } AddressBook; // 函数声明 void init_address_book(AddressBook* address_book); void add_contact(AddressBook* address_book); void print_address_book(const AddressBook* address_book); int is_valid_name(const char* name); int is_valid_phone_num(const char* phone_num); int main() { AddressBook address_book; init_address_book(&address_book); int option; do { printf("请选择操作:\n"); printf("1. 添加联系人\n"); printf("2. 查看通讯录\n"); printf("3. 退出\n"); printf(">> "); scanf("%d", &option); switch (option) { case 1: add_contact(&address_book); break; case 2: print_address_book(&address_book); break; case 3: printf("再见!\n"); break; default: printf("无效的选项!\n"); break; } } while (option != 3); free(address_book.contacts); return 0; } // 初始化通讯录 void init_address_book(AddressBook* address_book) { address_book->count = 0; address_book->capacity = 10; address_book->contacts = (Contact*) malloc(sizeof(Contact) * address_book->capacity); } // 添加联系人 void add_contact(AddressBook* address_book) { if (address_book->count >= address_book->capacity) { address_book->capacity *= 2; address_book->contacts = (Contact*) realloc(address_book->contacts, sizeof(Contact) * address_book->capacity); } Contact* contact = &address_book->contacts[address_book->count]; // 输入联系人姓名 printf("请输入联系人姓名:"); scanf("%s", contact->name); while (!is_valid_name(contact->name)) { printf("姓名不合法,请重新输入:"); scanf("%s", contact->name); } // 输入联系人电话号码 printf("请输入联系人电话号码:"); scanf("%s", contact->phone_num); while (!is_valid_phone_num(contact->phone_num)) { printf("电话号码不合法,请重新输入:"); scanf("%s", contact->phone_num); } printf("添加联系人成功!\n"); address_book->count++; } // 打印通讯录 void print_address_book(const AddressBook* address_book) { if (address_book->count == 0) { printf("通讯录为空!\n"); return; } printf("通讯录:\n"); for (int i = 0; i < address_book->count; i++) { const Contact* contact = &address_book->contacts[i]; printf("%d. %s %s\n", i+1, contact->name, contact->phone_num); } } // 判断姓名是否合法 int is_valid_name(const char* name) { // 姓名不能超过20个字符 if (strlen(name) > MAX_NAME_LEN) { return 0; } // 姓名不能包含数字 for (int i = 0; i < strlen(name); i++) { if (isdigit(name[i])) { return 0; } } return 1; } // 判断电话号码是否合法 int is_valid_phone_num(const char* phone_num) { // 电话号码必须是11位数字 if (strlen(phone_num) != MAX_PHONE_NUM_LEN) { return 0; } // 电话号码必须全部是数字 for (int i = 0; i < strlen(phone_num); i++) { if (!isdigit(phone_num[i])) { return 0; } } return 1; } ``` 在上面的代码中,我们定义了两个结构体,一个是联系人结构体,一个是通讯录结构体。我们使用动态数组来保存联系人信息,并在添加联系人进行容量检查和重新分配内存。我们还实现了两个函数来检查联系人姓名和电话号码是否合法。在添加联系人,我们会先让用户输入姓名和电话号码,并进行合法性检查,如果不合法就让用户重新输入。 希望这份示例代码能够帮助到您!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值