免费,无效指针
Free, invalid pointer
我有一个程序,可以根据分隔符拆分字符串。我还有 2 个其他函数,一个打印返回的数组,另一个释放数组。
我的程序打印数组,returns调用自由数组方法时出现错误。以下是完整代码。
#include "stringsplit.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
/* Split string by another string, return split parts + NULL in array.
*
* Parameters:
* str: the string to split
* split: the string to split str with
*
* Returns:
* A dynamically reserved array of dynamically reserved string parts.
*
* For example called with "Test string split" and " ",
* returns ["Test", "string", "split", NULL].
* Or called with "Another - test" and " - ",
* returns ["Another", "test", NULL].
*/
unsigned long int getNofTokens(const char *string) {
char *stringCopy;
unsigned long int stringLength;
unsigned long int count = 0;
stringLength = (unsigned)strlen(string);
stringCopy = malloc((stringLength + 1) * sizeof(char));
strcpy(stringCopy, string);
if (strtok(stringCopy, " \t") != NULL) {
count++;
while (strtok(NULL, " \t") != NULL)
count++;
}
free(stringCopy);
return count;
}
char **split_string(const char *str, const char *split) {
unsigned long int count = getNofTokens(str);
char **result;
result = malloc(sizeof(char *) * count + 1);
char *tmp = malloc(sizeof(char) * strlen(str));
strcpy(tmp, str);
char *token = strtok(tmp, split);
int idx = 0;
while (token != NULL) {
result[idx++] = token;
token = strtok(NULL, split);
}
return result;
}
void print_split_string(char **split_string) {
for (int i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (int i = 0; split_string[i] != NULL; i++) {
char *currentPointer = split_string[i];
free(currentPointer);
}
free(split_string);
}
此外,我需要在数组末尾显式添加 [=11=]
还是 strtok
自动添加?
调试时,记下您从 malloc
、strdup
等获得的值。我们将这些值称为“活动集”。这只是一个名称,以便我们可以参考它们。你从这些函数中得到一个指针,你在心里把它添加到活动集中。当您调用 free
时,您可以 仅 传递来自活动集的值,并且在释放 returns 之后,您可以将它们从集中删除。 free
的任何其他使用都是无效的,并且是一个错误。
您可以通过在所有内存分配之后放置断点轻松找到这一点,这样您就可以记下指针值,然后在所有释放上设置断点,这样您就可以查看这些指针值之一是否已传递给免费 - 因为,再一次,否则就是滥用 free
.
这也可以使用“printf”调试来完成。像这样:
char *buf = malloc(...); // or strdup, or ...
fprintf(stderr, "+++ Alloc %8p\n", buf);
然后只要有空,再做一次:
fprintf(stderr, "--- Free %8p\n", ptr);
free(ptr);
在程序的输出中,您必须能够将每个 +++
与 ---
匹配。如果你看到任何 ---
的值之前没有用 +++
列出,那就是你的问题:那是 free :)
的错误调用
我建议使用 fprintf(stderr, ...
而不是 printf(...
,因为前者通常是无缓冲的,因此如果您的程序崩溃,您不会错过任何输出。 printf
在某些架构上进行了缓冲(而在其他架构上未进行缓冲 - 为了保持一致性)。
您的代码中存在一些问题:
[Major] 函数getNofTokens()
不以分隔符字符串为参数,它统计由空格分隔的单词数,可能会向其调用者返回不一致的计数。
[Major] result = malloc(sizeof(char *) * count + 1);
中分配的大小不正确:应该是:
result = malloc(sizeof(char *) * (count + 1));
存储尾随 NULL
指针将超出分配的 space.
的末尾
[Major] 在数组末尾存储所述 NULL
终止符确实是必要的,因为 malloc()
未初始化。
[Major]无法安全释放由split_string
分配和解析的字符串副本,因为指针tmp
没有保存在任何地方。在两种情况下,指向第一个标记的指针将不同于 tmp
:如果字符串仅包含定界符(未找到标记)或者如果字符串以定界符开头(将跳过初始定界符)。为了简化代码并使其可靠,可以复制每个令牌并释放 tmp
。事实上,您的 free_split_string()
函数依赖于此行为。对于当前的实现,行为是未定义的。
[Minor] 您对字符串长度和数组索引变量使用 unsigned long
和 int
不一致。为了保持一致性,您应该对两者都使用 size_t
。
[备注] 你应该用 strdup()
分配字符串副本。如果此 POSIX 标准函数在您的系统上不可用,请编写一个简单的实现。
[主要] 你从不测试内存分配失败。这可以用于测试目的并丢弃代码,但应始终在生产代码中考虑此类潜在故障。
[备注] strtok()
是一个使用起来很棘手的函数:它修改源字符串并保持隐藏的静态状态,使其不-可重入。您应该避免使用此函数,尽管在这种特殊情况下它可以正确执行,但如果 split_string
或 getNofTokens
的调用者依赖于保留此隐藏状态,则会出现意外行为。
这是修改后的版本:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "stringsplit.h"
/* Split string by another string, return split parts + NULL in array.
*
* Parameters:
* str: the string to split
* split: the string to split str with
*
* Returns:
* A dynamically reserved array of dynamically reserved string parts.
*
* For example called with "Test string split" and " ",
* returns ["Test", "string", "split", NULL].
* Or called with "Another - test" and " - ",
* returns ["Another", "test", NULL].
*/
size_t getNofTokens(const char *string, const char *split) {
char *tmp = strdup(string);
size_t count = 0;
if (strtok(tmp, split) != NULL) {
count++;
while (strtok(NULL, split) != NULL)
count++;
}
free(tmp);
return count;
}
char **split_string(const char *str, const char *split) {
size_t count = getNofTokens(str, split);
char **result = malloc(sizeof(*result) * (count + 1));
char *tmp = strdup(str);
char *token = strtok(tmp, split);
size_t idx = 0;
while (token != NULL && idx < count) {
result[idx++] = strdup(token);
token = strtok(NULL, split);
}
result[idx] = NULL;
free(tmp);
return result;
}
void print_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
free(split_string[i]);
}
free(split_string);
}
这是一个没有 strtok()
且没有中间分配的替代方案:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "stringsplit.h"
size_t getNofTokens(const char *str, const char *split) {
size_t count = 0;
size_t pos = 0, len;
for (pos = 0;; pos += len) {
pos += strspn(str + pos, split); // skip delimiters
len = strcspn(str + pos, split); // parse token
if (len == '[=12=]')
break;
count++;
}
return count;
}
char **split_string(const char *str, const char *split) {
size_t count = getNofTokens(str, split);
char **result = malloc(sizeof(*result) * (count + 1));
size_t pos, len, idx;
for (pos = 0, idx = 0; idx < count; pos += len, idx++) {
pos += strspn(str + pos, split); // skip delimiters
len = strcspn(str + pos, split); // parse token
if (len == '[=12=]')
break;
result[idx] = strndup(str + pos, len);
}
result[idx] = NULL;
return result;
}
void print_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
free(split_string[i]);
}
free(split_string);
}
EDIT 在重新阅读您评论中的规范后,似乎对 split
参数的语义存在一些潜在的混淆:
- 如果
split
是一组定界符,上面的代码就可以完成工作。示例将按预期拆分。
- 如果
split
是要显式匹配的实际字符串,则上述代码仅适用于评论中给出的示例。
要实现后一种语义,您应该使用 strstr()
在 getNofTokens
和 split_string
.
中搜索 split
子字符串
这是一个例子:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "stringsplit.h"
/* Split string by another string, return split parts + NULL in array.
*
* Parameters:
* str: the string to split
* split: the string to split str with
*
* Returns:
* A dynamically reserved array of dynamically reserved string parts.
*
* For example called with "Test string split" and " ",
* returns ["Test", "string", "split", NULL].
* Or called with "Another - test" and " - ",
* returns ["Another", "test", NULL].
*/
size_t getNofTokens(const char *str, const char *split) {
const char *p;
size_t count = 1;
size_t len = strlen(split);
if (len == 0)
return strlen(str);
for (p = str; (p = strstr(p, split)) != NULL; p += len)
count++;
return count;
}
char **split_string(const char *str, const char *split) {
size_t count = getNofTokens(str, split);
char **result = malloc(sizeof(*result) * (count + 1));
size_t len = strlen(split);
size_t idx;
const char *p = str;
for (idx = 0; idx < count; idx++) {
const char *q = strstr(p, split);
if (q == NULL) {
q = p + strlen(p);
} else
if (q == p && *q != '[=13=]') {
q++;
}
result[idx] = strndup(p, q - p);
p = q + len;
}
result[idx] = NULL;
return result;
}
void print_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
free(split_string[i]);
}
free(split_string);
}
我有一个程序,可以根据分隔符拆分字符串。我还有 2 个其他函数,一个打印返回的数组,另一个释放数组。
我的程序打印数组,returns调用自由数组方法时出现错误。以下是完整代码。
#include "stringsplit.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
/* Split string by another string, return split parts + NULL in array.
*
* Parameters:
* str: the string to split
* split: the string to split str with
*
* Returns:
* A dynamically reserved array of dynamically reserved string parts.
*
* For example called with "Test string split" and " ",
* returns ["Test", "string", "split", NULL].
* Or called with "Another - test" and " - ",
* returns ["Another", "test", NULL].
*/
unsigned long int getNofTokens(const char *string) {
char *stringCopy;
unsigned long int stringLength;
unsigned long int count = 0;
stringLength = (unsigned)strlen(string);
stringCopy = malloc((stringLength + 1) * sizeof(char));
strcpy(stringCopy, string);
if (strtok(stringCopy, " \t") != NULL) {
count++;
while (strtok(NULL, " \t") != NULL)
count++;
}
free(stringCopy);
return count;
}
char **split_string(const char *str, const char *split) {
unsigned long int count = getNofTokens(str);
char **result;
result = malloc(sizeof(char *) * count + 1);
char *tmp = malloc(sizeof(char) * strlen(str));
strcpy(tmp, str);
char *token = strtok(tmp, split);
int idx = 0;
while (token != NULL) {
result[idx++] = token;
token = strtok(NULL, split);
}
return result;
}
void print_split_string(char **split_string) {
for (int i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (int i = 0; split_string[i] != NULL; i++) {
char *currentPointer = split_string[i];
free(currentPointer);
}
free(split_string);
}
此外,我需要在数组末尾显式添加 [=11=]
还是 strtok
自动添加?
调试时,记下您从 malloc
、strdup
等获得的值。我们将这些值称为“活动集”。这只是一个名称,以便我们可以参考它们。你从这些函数中得到一个指针,你在心里把它添加到活动集中。当您调用 free
时,您可以 仅 传递来自活动集的值,并且在释放 returns 之后,您可以将它们从集中删除。 free
的任何其他使用都是无效的,并且是一个错误。
您可以通过在所有内存分配之后放置断点轻松找到这一点,这样您就可以记下指针值,然后在所有释放上设置断点,这样您就可以查看这些指针值之一是否已传递给免费 - 因为,再一次,否则就是滥用 free
.
这也可以使用“printf”调试来完成。像这样:
char *buf = malloc(...); // or strdup, or ...
fprintf(stderr, "+++ Alloc %8p\n", buf);
然后只要有空,再做一次:
fprintf(stderr, "--- Free %8p\n", ptr);
free(ptr);
在程序的输出中,您必须能够将每个 +++
与 ---
匹配。如果你看到任何 ---
的值之前没有用 +++
列出,那就是你的问题:那是 free :)
我建议使用 fprintf(stderr, ...
而不是 printf(...
,因为前者通常是无缓冲的,因此如果您的程序崩溃,您不会错过任何输出。 printf
在某些架构上进行了缓冲(而在其他架构上未进行缓冲 - 为了保持一致性)。
您的代码中存在一些问题:
[Major] 函数
getNofTokens()
不以分隔符字符串为参数,它统计由空格分隔的单词数,可能会向其调用者返回不一致的计数。[Major]
result = malloc(sizeof(char *) * count + 1);
中分配的大小不正确:应该是:result = malloc(sizeof(char *) * (count + 1));
存储尾随
的末尾NULL
指针将超出分配的 space.[Major] 在数组末尾存储所述
NULL
终止符确实是必要的,因为malloc()
未初始化。[Major]无法安全释放由
split_string
分配和解析的字符串副本,因为指针tmp
没有保存在任何地方。在两种情况下,指向第一个标记的指针将不同于tmp
:如果字符串仅包含定界符(未找到标记)或者如果字符串以定界符开头(将跳过初始定界符)。为了简化代码并使其可靠,可以复制每个令牌并释放tmp
。事实上,您的free_split_string()
函数依赖于此行为。对于当前的实现,行为是未定义的。[Minor] 您对字符串长度和数组索引变量使用
unsigned long
和int
不一致。为了保持一致性,您应该对两者都使用size_t
。[备注] 你应该用
strdup()
分配字符串副本。如果此 POSIX 标准函数在您的系统上不可用,请编写一个简单的实现。[主要] 你从不测试内存分配失败。这可以用于测试目的并丢弃代码,但应始终在生产代码中考虑此类潜在故障。
[备注]
strtok()
是一个使用起来很棘手的函数:它修改源字符串并保持隐藏的静态状态,使其不-可重入。您应该避免使用此函数,尽管在这种特殊情况下它可以正确执行,但如果split_string
或getNofTokens
的调用者依赖于保留此隐藏状态,则会出现意外行为。
这是修改后的版本:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "stringsplit.h"
/* Split string by another string, return split parts + NULL in array.
*
* Parameters:
* str: the string to split
* split: the string to split str with
*
* Returns:
* A dynamically reserved array of dynamically reserved string parts.
*
* For example called with "Test string split" and " ",
* returns ["Test", "string", "split", NULL].
* Or called with "Another - test" and " - ",
* returns ["Another", "test", NULL].
*/
size_t getNofTokens(const char *string, const char *split) {
char *tmp = strdup(string);
size_t count = 0;
if (strtok(tmp, split) != NULL) {
count++;
while (strtok(NULL, split) != NULL)
count++;
}
free(tmp);
return count;
}
char **split_string(const char *str, const char *split) {
size_t count = getNofTokens(str, split);
char **result = malloc(sizeof(*result) * (count + 1));
char *tmp = strdup(str);
char *token = strtok(tmp, split);
size_t idx = 0;
while (token != NULL && idx < count) {
result[idx++] = strdup(token);
token = strtok(NULL, split);
}
result[idx] = NULL;
free(tmp);
return result;
}
void print_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
free(split_string[i]);
}
free(split_string);
}
这是一个没有 strtok()
且没有中间分配的替代方案:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "stringsplit.h"
size_t getNofTokens(const char *str, const char *split) {
size_t count = 0;
size_t pos = 0, len;
for (pos = 0;; pos += len) {
pos += strspn(str + pos, split); // skip delimiters
len = strcspn(str + pos, split); // parse token
if (len == '[=12=]')
break;
count++;
}
return count;
}
char **split_string(const char *str, const char *split) {
size_t count = getNofTokens(str, split);
char **result = malloc(sizeof(*result) * (count + 1));
size_t pos, len, idx;
for (pos = 0, idx = 0; idx < count; pos += len, idx++) {
pos += strspn(str + pos, split); // skip delimiters
len = strcspn(str + pos, split); // parse token
if (len == '[=12=]')
break;
result[idx] = strndup(str + pos, len);
}
result[idx] = NULL;
return result;
}
void print_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
free(split_string[i]);
}
free(split_string);
}
EDIT 在重新阅读您评论中的规范后,似乎对 split
参数的语义存在一些潜在的混淆:
- 如果
split
是一组定界符,上面的代码就可以完成工作。示例将按预期拆分。 - 如果
split
是要显式匹配的实际字符串,则上述代码仅适用于评论中给出的示例。
要实现后一种语义,您应该使用 strstr()
在 getNofTokens
和 split_string
.
split
子字符串
这是一个例子:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "stringsplit.h"
/* Split string by another string, return split parts + NULL in array.
*
* Parameters:
* str: the string to split
* split: the string to split str with
*
* Returns:
* A dynamically reserved array of dynamically reserved string parts.
*
* For example called with "Test string split" and " ",
* returns ["Test", "string", "split", NULL].
* Or called with "Another - test" and " - ",
* returns ["Another", "test", NULL].
*/
size_t getNofTokens(const char *str, const char *split) {
const char *p;
size_t count = 1;
size_t len = strlen(split);
if (len == 0)
return strlen(str);
for (p = str; (p = strstr(p, split)) != NULL; p += len)
count++;
return count;
}
char **split_string(const char *str, const char *split) {
size_t count = getNofTokens(str, split);
char **result = malloc(sizeof(*result) * (count + 1));
size_t len = strlen(split);
size_t idx;
const char *p = str;
for (idx = 0; idx < count; idx++) {
const char *q = strstr(p, split);
if (q == NULL) {
q = p + strlen(p);
} else
if (q == p && *q != '[=13=]') {
q++;
}
result[idx] = strndup(p, q - p);
p = q + len;
}
result[idx] = NULL;
return result;
}
void print_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
printf("%s\n", split_string[i]);
}
}
void free_split_string(char **split_string) {
for (size_t i = 0; split_string[i] != NULL; i++) {
free(split_string[i]);
}
free(split_string);
}