4

注意:当我在这里说“静态字符串”时,我指的是 realloc 无法处理的内存。

嗨,我编写了一个带有 char * 参数的过程,如果内存不能通过 realloc 重新定位/调整大小,我想创建一个副本。照原样,该过程是一个“重”字符串处理器,因此无知并复制字符串是否是静态的肯定会在将来导致一些内存开销/处理问题。

我尝试使用异常处理程序来修改静态字符串,应用程序只是退出而没有任何通知。我退后一步,看着 C 说:“我没印象。” 如果我听说过一个,那将是一个例外。

我尝试使用异常处理程序在静态变量上调用 realloc ... Glib 报告它无法找到结构的一些私有信息(我确定)我不知道并且显然在程序上调用 abort意味着它不是可以用 longjmp/setjmp 或 C++ try, catch finally 捕获的异常。

我很确定必须有一种方法可以合理地做到这一点。例如,动态内存很可能不在静态内存附近的任何地方,所以如果有办法从地址泄露这些信息......我们可能只是有宾果游戏......

我不确定 C/C++ 预处理器中是否有任何宏可以识别宏参数的来源和类型,但如果没有,那就太愚蠢了。宏汇编器对这类事情非常聪明。从缺乏强大的错误处理来看,如果没有,我不会感到惊讶。

4

5 回答 5

5

C 没有提供一种可移植的方式来区分静态分配的内存块和动态分配的内存块。struct您可以使用字符串指针和指示对象占用的内存类型的标志来构建自己的。在 C++ 中,您可以使它成为一个具有两个不同构造函数的类,每个内存类型一个,以使您的生活更轻松。

就中止程序而言,尝试释放或重新分配尚未动态分配的内存是未定义的行为,因此中止是一个公平的游戏。

于 2012-04-11T00:09:21.443 回答
3

您可能能够检测内存范围并进行一些指针比较。我在一些垃圾收集代码中完成了这项工作,我需要知道指针是在堆栈、堆还是其他地方。

如果您控制所有分配,您可以简单地根据从或出来malloc的每个动态指针保持最小和最大界限。小于 min 或大于 max 的指针可能不在堆中,并且这个和分隔的区域不太可能与任何静态区域相交。如果您知道一个指针是静态的或者它来自 malloc,并且该指针位于 malloced 存储的“边界框”之外,那么它必须是静态的。callocreallocminmax

有一些“博物馆”机器,这类东西不起作用,C 标准没有给出使用关系运算符比较指向不同对象的指针的含义,除了完全相等或不相等。

于 2012-04-11T00:08:26.603 回答
2

您将获得的任何解决方案都是特定于平台的,因此您可能希望指定您正在运行的平台。

至于为什么abort在传递意外参数时应该调用库,这往往比继续执行更安全。当然,这更烦人,但此时库知道调用它的代码处于无法恢复的状态。

于 2012-04-11T00:07:54.350 回答
1

我编写了一个带有 char * 参数的过程,如果内存不能通过 realloc 重新定位/调整大小,我想创建一个副本。

从根本上说,问题在于您想根据您正在操作的范围内不可用的信息进行内存管理。显然,您在创建字符串时知道该字符串是在堆栈上还是在堆上,但该信息是当你在你的函数中时迷失了。试图解决这个问题几乎是不可能的,而且肯定超出了标准。

我尝试使用异常处理程序来修改静态字符串,应用程序只是退出而没有任何通知。我退后一步,看着 C 说:“我没印象。” 如果我听说过一个,那将是一个例外。

如前所述,C 没有例外。C++可以做到这一点,但 C++ 标准委员会认为,让 C 函数在 C++ 中表现不同将是一场噩梦。

我很确定必须有一种方法可以合理地做到这一点。

您可以让您的应用程序使用您创建的堆栈替换默认堆栈(因此,知道其中的地址范围)ucontext.hWindows Fibers,并检查地址是否在该范围内。但是,(1)这会给使用你的库的任何应用程序带来巨大的负担(当然,如果你使用你的库编写了唯一的应用程序,那么你可能愿意接受这个负担);realloc并且 (2) 没有检测到由于其他原因(使用static分配、使用自定义分配器分配、在 Windows 上使用SysAllocor分配、在 C++ 中使用等分配)而无法使用的内存。HeapAllocnew

相反,我建议让您的函数采用一个函数指针,该指针将指向一个用于重新分配内存的函数。如果函数指针是NULL,则复制内存。否则,您调用该函数。

于 2012-04-11T00:23:09.913 回答
0

原始海报在这里。我没有提到我有一个可行的解决方案来解决这个问题,它并不像我希望的那样强大。请不要难过,我感谢参与此征求意见和回答的每个人。“程序”本质in question上是variadic不超过 63 个匿名char *参数。

它是什么:一个多字符串连接器。它可以处理许多参数,但我建议开发人员不要传递超过 20 个左右的参数。开发人员从不直接调用该过程。相反,一个称为“过程名称”的宏将参数和一个尾随的空指针一起传递,所以我知道什么时候我已经遇到了统计数据收集的结束。

如果函数只接收两个参数,我创建第一个参数的副本并返回该指针。这是字符串文字大小写。但实际上它所做的只是掩盖strdup

如果单个有效参数测试失败,我们继续使用来自包含每个指针reallocmemcpy它的 64 条记录的静态数据库中的记录信息strlen,每次将 memcopy 的大小添加到memcpy destination作为返回副本开始的辅助指针 ()来自 realloc 的值。

我编写了第二个带有“d”附件的宏,表示第一个参数不是动态的,因此需要一个动态参数,并且该宏使用以下代码将动态参数注入到实际的过程调用中第一个论点:

strdup("")

它是一个可以重新分配的有效内存块。它strlen返回 0,因此当循环将它的大小添加到记录中时,它不会产生任何影响。空终止符将被覆盖memcpy。我应该说它工作得非常好。但是,仅在过去几周内才刚接触 C,我不明白您不能“傻瓜证明”这些东西。我想人们会按照指示或在 DLL 地狱中结束。

该代码在没有所有这些额外的恶作剧和口哨声的情况下工作得很好,但是没有办法回报单个内存块,由于所有动态指针 mgmt,该过程在循环处理中丢失。涉及。因此,第一个参数必须始终是动态的。我读到有人建议在函数中使用保存指针的 c-static 变量,但是您不能使用该过程在其他函数中执行其他操作,例如在决定编译的递归下降解析器中需要随着它的进行。

如果您想查看代码,请询问!

快乐编码!


mkstr.cpp

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

struct mkstr_record {
    size_t size;
    void *location;
};

// use the mkstr macro (in mkstr.h) to call this procedure.
// The first argument to mkstr MUST BE dynamically allocated. i.e.: by malloc(),
// or strdup(), unless that argument is the sole argument to mkstr. Calling mkstr()
// with a single argument is functionally equivalent to calling strdup() on the same
// address.
char *mkstr_(char *source, ...) {

    va_list args;

    size_t length = 0, item = 0;

    mkstr_record list[64]; /*

    maximum of 64 input vectors. this goes beyond reason!

    the result of this procedure is a string that CAN be
    concatenated by THIS procedure, or further more reallocated!

    We could probably count the arguments and initialize properly,
    but this function shouldn't be used to concatenate more than 20
    vectors per call. Unless you are just "asking for it".

    In any case, develop a workaround. Thank yourself later.

    */// Argument Range Will Not Be Validated. Caller Beware!!!

    va_start(args, source);

    char *thisArg = source;

        while (thisArg) {

            // don't validate list bounds here.
            // an if statement here is too costly for
            // for the meager benefit it can provide.

            length += list[item].size = strlen(thisArg);
            list[item].location = thisArg;
            thisArg = va_arg(args, char *);
            item++;

        }

    va_end(args);

    if (item == 1) return strdup(source);   // single argument: fail-safe

    length++;   // final zero terminator index.

    char *str = (char *) realloc(source, length);

    if (!str) return str;   // don't care. memory error. check your work.

    thisArg = (str + list[0].size);

    size_t count = item;

    for (item = 1; item < count; item++) {
        memcpy(thisArg, list[item].location, list[item].size);
        thisArg += list[item].size;
    }

    *(thisArg) = '\0';  // terminate the string.

    return str;

}


mkstr.h

#ifndef MKSTR_H_
#define MKSTR_H_

extern char *mkstr_(char *string, ...);

// This macro ensures that the final argument to "mkstr" is null.
// arguments: const char *, ...
// limitation: 63 variable arguments max.
// stipulation: caller must free returned pointer.

#define mkstr(str, args...) mkstr_(str, ##args, NULL)
#define mkstrd(str, args...) mkstr_(strdup(str), ##args, NULL)

/* calling mkstr with more than 64 arguments should produce a segmentation fault
 * this is not a bug. it is intentional operation. The price of saving an in loop
 * error check comes at the cost of writing code that looks good and works great.
 *
 * If you need a babysitter, find a new function [period]
*/


#endif /* MKSTR_H_ */

不要在演职员表中提及我。她很好,花花公子。

于 2012-04-11T01:40:53.197 回答