C 中的断言和错误检查
Assert and error-checking in C
我在 code review 上发布了我的代码,得到的答复是我没有进行错误检查。据我所知,我可以通过多种方式进行错误检查:
- 前置条件和后置条件的断言
perror
用法之类的,我有时会,但我还没有学会
- 使用来自函数或类似函数的布尔值 return 手动编码错误检查,指示调用成功或失败
是否有更多方法或推荐的特定方法?同时使用 perror
和断言是更好还是更坏?代码在 my github 中,main
循环如下所示:
int main(int argc, char *argv[]) {
bool donotrun = false;
struct sigaction new_action, old_action;
hashtable_t *hashtable = ht_create(65536);
/* Set up the structure to specify the new action. */
new_action.sa_handler = termination_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction(SIGINT, &new_action, NULL);
sigaction(SIGHUP, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction(SIGHUP, &new_action, NULL);
sigaction(SIGTERM, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction(SIGTERM, &new_action, NULL);
void *pParser;
char *c;
int index = 0;
int i;
char *cvalue = NULL;
const char *commandFile = "";
bool quietFlag;
while (1) {
index = 0;
i = getopt_long(argc, argv, "pc:vh",
options, &index);
if (i == -1)
break;
switch (i) {
case 'p': {
exit(EXIT_SUCCESS);
}
case 'v': {
printf("sh OpenShell version 0.1(a)\n");
printf("Version: %s\n", VERSION);
exit(EXIT_SUCCESS);
}
case 'h': {
usage();
exit(EXIT_SUCCESS);
}
case 'c': {
cvalue = optarg;
command(cvalue, hashtable);
exit(EXIT_SUCCESS);
}
case 'f':
/*
* Execute commands from file.
* This is used for osh script files.
* The quiet flag is also set.
*/
if ((argc != 1) || commandFile)
usage();
quietFlag = true;
argc--;
break;
case '?':
if (optopt == 'c')
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
else if (isprint (optopt))
fprintf(stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf(stderr,
"Unknown option character `\x%x'.\n",
optopt);
default: {
return 1;
}
}
}
getPath();
pParser = (void *) ParseAlloc(malloc);
char *copy = "";
for (; ;) {
bool scanning = true;
bool calc = true;
while (scanning) {
char *line = NULL;
line = readline("$ ");
if (line == NULL) {
/* No more lines, so exit the loop. */
break;
}
if (line)
copy = strdup(line);
if (line && !strstr(line, "for") && !strstr(line, "==") && !strstr(line, "if") && strstr(line, "=")) {
donotrun = true;
char str[128];
char *ptr;
strcpy(str, line);
strtok_r (str, "=", &ptr);
ht_set(hashtable, str, ptr);
}
if (!scanning)
break;
if (!isatty(fileno(stdin))) {
*argv++;
readFile(*argv++, hashtable);
free(line);
exit(0);
}
else {
if (!donotrun) {
line = str_replace(line, " | ", "|");
line = str_replace(line, " |", "|");
command(line, hashtable);
}
donotrun = false;
add_history(copy);
}
free(copy);
}
}
// ParseFree(pParser, free);FIXME: where should this go?
return 0;
}
然后我断言我必须在开发过程中注释掉,现在我不确定断言应该是什么。
/* Returns a struct that has the number of "chunks" the list of chunks.
* Splits the command by char | and then by whitespace and return a list of struct pointers
*/
struct str_list *list_split(const char *a_str, char *a_delim) {
char **result = 0;
size_t count = 0;
char *tmp = (char *) a_str;
char *ctmp;
char *token = "";
char *last_comma = 0;
char *tmp2 = (char *) a_str; /* TODO: This variable can reuse tmp */
//char *delim[2];
//delim[0] = a_delim;
struct str_list *chunks = NULL;
/* Count how many elements will be extracted. */
while (*tmp) {
if (*a_delim == *tmp) {
count++;
last_comma = tmp;
}
tmp++;
}
/* Add space for trailing token. */
count += last_comma < (a_str + strlen(a_str) - 1);
count++;
result = alloc_argv(count);
char **tmpresult = alloc_argv(count);
chunks = malloc(count * sizeof *chunks);
if (result == NULL) {
printf("Error allocating memory!\n");
return chunks;;
}
if (result) {
size_t idx = 0;
token = strtok((char *) strdup(a_str), "|");
int a = 0;
while (token) {
// assert(idx < count); Why must I turn off assertions?
tmpresult[a] = strdup(token);
a++;
ctmp = strdup(token);
*(result + idx++) = ctmp; /* memory leak! how to free() */;
token = strtok(0, a_delim);
}
// assert(idx == count - 1);
*(result + idx) = 0;
}
chunks->argv = alloc_argv(BUFFER_SIZE);//malloc(chunks->size * sizeof(char *) * 1);
int i = 0;
chunks = tokenize(&i, chunks, result, count, tmp2);
chunks->pipes = i; /* important! to get this right */
free(ctmp);
return expand_shell(tmpresult, chunks);
}
据我所知,断言应该只在开发和测试期间使用。 assert 实际上是一个宏。当您构建调试配置时,它会执行检查并在错误时退出。当您处于发布配置时,它什么都不做。因此应该使用断言来检查编程错误。
http://www.cplusplus.com/reference/cassert/assert/
你不能用 assert 做的是检查运行时错误,这意味着在程序执行期间由于环境或用户输入而发生的错误,例如程序无法打开或写入文件,用户在需要数字时输入字符串,无法建立网络连接等。您希望在调试和发布模式下都进行这些检查。
我认为您提到的另外两种错误检查方法,perror 和检查 return 值,总是一起使用。通常检查函数的 return 值,然后打印一条消息(使用 perror 或其他函数),然后进行错误恢复或退出。 perror 本身只是打印一条消息,除非您执行其他操作,否则程序将继续。
http://www.cplusplus.com/reference/cstdio/perror/
通常应该检查外部函数调用的 return 值,并检查程序的所有输入是否正确。大多数库函数都可能失败,包括 malloc 或 printf,但根据您的偏执程度,仅检查最有可能导致问题的函数(文件、网络、库或设备初始化等)就足够了,具体取决于您的应用程序。
我在 code review 上发布了我的代码,得到的答复是我没有进行错误检查。据我所知,我可以通过多种方式进行错误检查:
- 前置条件和后置条件的断言
perror
用法之类的,我有时会,但我还没有学会- 使用来自函数或类似函数的布尔值 return 手动编码错误检查,指示调用成功或失败
是否有更多方法或推荐的特定方法?同时使用 perror
和断言是更好还是更坏?代码在 my github 中,main
循环如下所示:
int main(int argc, char *argv[]) {
bool donotrun = false;
struct sigaction new_action, old_action;
hashtable_t *hashtable = ht_create(65536);
/* Set up the structure to specify the new action. */
new_action.sa_handler = termination_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction(SIGINT, &new_action, NULL);
sigaction(SIGHUP, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction(SIGHUP, &new_action, NULL);
sigaction(SIGTERM, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
sigaction(SIGTERM, &new_action, NULL);
void *pParser;
char *c;
int index = 0;
int i;
char *cvalue = NULL;
const char *commandFile = "";
bool quietFlag;
while (1) {
index = 0;
i = getopt_long(argc, argv, "pc:vh",
options, &index);
if (i == -1)
break;
switch (i) {
case 'p': {
exit(EXIT_SUCCESS);
}
case 'v': {
printf("sh OpenShell version 0.1(a)\n");
printf("Version: %s\n", VERSION);
exit(EXIT_SUCCESS);
}
case 'h': {
usage();
exit(EXIT_SUCCESS);
}
case 'c': {
cvalue = optarg;
command(cvalue, hashtable);
exit(EXIT_SUCCESS);
}
case 'f':
/*
* Execute commands from file.
* This is used for osh script files.
* The quiet flag is also set.
*/
if ((argc != 1) || commandFile)
usage();
quietFlag = true;
argc--;
break;
case '?':
if (optopt == 'c')
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
else if (isprint (optopt))
fprintf(stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf(stderr,
"Unknown option character `\x%x'.\n",
optopt);
default: {
return 1;
}
}
}
getPath();
pParser = (void *) ParseAlloc(malloc);
char *copy = "";
for (; ;) {
bool scanning = true;
bool calc = true;
while (scanning) {
char *line = NULL;
line = readline("$ ");
if (line == NULL) {
/* No more lines, so exit the loop. */
break;
}
if (line)
copy = strdup(line);
if (line && !strstr(line, "for") && !strstr(line, "==") && !strstr(line, "if") && strstr(line, "=")) {
donotrun = true;
char str[128];
char *ptr;
strcpy(str, line);
strtok_r (str, "=", &ptr);
ht_set(hashtable, str, ptr);
}
if (!scanning)
break;
if (!isatty(fileno(stdin))) {
*argv++;
readFile(*argv++, hashtable);
free(line);
exit(0);
}
else {
if (!donotrun) {
line = str_replace(line, " | ", "|");
line = str_replace(line, " |", "|");
command(line, hashtable);
}
donotrun = false;
add_history(copy);
}
free(copy);
}
}
// ParseFree(pParser, free);FIXME: where should this go?
return 0;
}
然后我断言我必须在开发过程中注释掉,现在我不确定断言应该是什么。
/* Returns a struct that has the number of "chunks" the list of chunks.
* Splits the command by char | and then by whitespace and return a list of struct pointers
*/
struct str_list *list_split(const char *a_str, char *a_delim) {
char **result = 0;
size_t count = 0;
char *tmp = (char *) a_str;
char *ctmp;
char *token = "";
char *last_comma = 0;
char *tmp2 = (char *) a_str; /* TODO: This variable can reuse tmp */
//char *delim[2];
//delim[0] = a_delim;
struct str_list *chunks = NULL;
/* Count how many elements will be extracted. */
while (*tmp) {
if (*a_delim == *tmp) {
count++;
last_comma = tmp;
}
tmp++;
}
/* Add space for trailing token. */
count += last_comma < (a_str + strlen(a_str) - 1);
count++;
result = alloc_argv(count);
char **tmpresult = alloc_argv(count);
chunks = malloc(count * sizeof *chunks);
if (result == NULL) {
printf("Error allocating memory!\n");
return chunks;;
}
if (result) {
size_t idx = 0;
token = strtok((char *) strdup(a_str), "|");
int a = 0;
while (token) {
// assert(idx < count); Why must I turn off assertions?
tmpresult[a] = strdup(token);
a++;
ctmp = strdup(token);
*(result + idx++) = ctmp; /* memory leak! how to free() */;
token = strtok(0, a_delim);
}
// assert(idx == count - 1);
*(result + idx) = 0;
}
chunks->argv = alloc_argv(BUFFER_SIZE);//malloc(chunks->size * sizeof(char *) * 1);
int i = 0;
chunks = tokenize(&i, chunks, result, count, tmp2);
chunks->pipes = i; /* important! to get this right */
free(ctmp);
return expand_shell(tmpresult, chunks);
}
据我所知,断言应该只在开发和测试期间使用。 assert 实际上是一个宏。当您构建调试配置时,它会执行检查并在错误时退出。当您处于发布配置时,它什么都不做。因此应该使用断言来检查编程错误。
http://www.cplusplus.com/reference/cassert/assert/
你不能用 assert 做的是检查运行时错误,这意味着在程序执行期间由于环境或用户输入而发生的错误,例如程序无法打开或写入文件,用户在需要数字时输入字符串,无法建立网络连接等。您希望在调试和发布模式下都进行这些检查。
我认为您提到的另外两种错误检查方法,perror 和检查 return 值,总是一起使用。通常检查函数的 return 值,然后打印一条消息(使用 perror 或其他函数),然后进行错误恢复或退出。 perror 本身只是打印一条消息,除非您执行其他操作,否则程序将继续。
http://www.cplusplus.com/reference/cstdio/perror/
通常应该检查外部函数调用的 return 值,并检查程序的所有输入是否正确。大多数库函数都可能失败,包括 malloc 或 printf,但根据您的偏执程度,仅检查最有可能导致问题的函数(文件、网络、库或设备初始化等)就足够了,具体取决于您的应用程序。