【问题标题】:How to detect if memory is dynamic or static, from a callee perspective?如何从被调用者的角度检测内存是动态的还是静态的?
【发布时间】:2012-04-23 07:09:48
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 从偏移量中明确一点:没有办法以独立于平台的方式做到这一点。
  • "我尝试使用异常处理程序来修改静态字符串,应用程序在没有任何通知的情况下退出。我退后一步,看着 C 说:“我没有留下深刻的印象。 “如果我听说过,那将是一个例外。” - Will,考虑到在 C 中没有可以处理的异常的概念(即,没有异常处理程序之类的东西)我认为这是很合理。您希望如何处理修改只读内存?
  • 这是一个问题还是一个咆哮?
  • 哇!从来没有一次得到这么多很棒的答案!
  • @TristonJ.Taylor:从您的 cmets,我得到的印象是您正在使用 C 进行编码。您想要 C 还是 C++ 的答案?

标签: c++ c realloc static-memory-allocation


【解决方案1】:

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

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

【讨论】:

  • 非常感谢您的意见!我同意 Oli:在 C 中实现的最佳方式。
  • 虽然我会放弃结构而只使用 typedef。进行预处理器类型验证时,浪费内存毫无意义。
  • @TristonJ.Taylor:这不能用 typedefs 解决(至少在 C 中不能解决,因为 C 中的函数不能重载)。
  • 它肯定会强制调用者转换一个无效的指针。这是我可以在文件中避免他们的东西。它是一个单一的函数,应该期望一个类型并返回相同的类型。因此需要实现 free、malloc 和 realloc 的包装函数。顶部的樱桃是一个宏,可以将类型转换回 char *
  • 不包括包装器...所有这些基本上都是免费的。
【解决方案2】:

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

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

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

【讨论】:

  • 这是我最喜欢的答案!我可以告诉你对 C. 博物馆的作品有过疯狂的经历!优雅!尽管我不愿承认:专家已经证实!文档 + 类型限定是(今天)的唯一途径。
  • 谢谢您,先生,您确实为问题提供了充分的答案。
  • 我最近在 TXR 语言的解释器中做的另一件事是实现几种字符串。这些都可以互换使用,但垃圾收集器知道不要踩到那些是静态的。它们由直接插入指针的两位类型代码标识。宏 lit("foo bar") 采用由字符串文字产生的 wchar_t * 指针,并将两个最低有效位更改为文字的类型代码。另一方面,动态字符串是指向更复杂的堆分配结构的指针。它们可以互换使用。
  • 这种方法无效。即使您假设单独对象之间的指针比较有效,字符串文字或其他具有静态存储持续时间的字符串也很可能出现在两个malloc 获得的字符串之间。只要静态字符串位于共享库中,并且两个malloc 获得的字符串恰好分配在加载共享库的区域的相对两侧(例如通过mmap),就会发生这种情况。 OP 的问题只是从根本上被误导了,但提出这个破碎而危险的答案并不好,即使它能让你得到一些代表......
  • 确实,该方法必须针对在虚拟内存中间映射的共享库使用进行微调。
【解决方案3】:

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

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

【讨论】:

  • 我不明白从被赋予无效指针返回 null 是如何无法返回的条件。如果我们正在破坏堆栈或进行一些代码注入,但不是在用户可访问的内存 i/o 'cludge' 中,这是有道理的。我想我应该庆幸它失败了。它是我必须生成的少一份错误报告。但它让“app-go-boom”很糟糕
  • 是的,返回 null,设置错误代码 EINVALID_MPTR,您现在可以可靠地检测静态或动态内存。然而,它似乎是某种“疯狂的代码”安全功能。只有保安才这么粗鲁!
  • 大声笑 听起来很熟悉? “很抱歉,您的通话无法按拨号完成……” 将两个主题交叉链接:“您的服务现在将不可撤销地终止。”
  • 再次拨打电话时发生了什么?这太粗鲁了!
【解决方案4】:

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

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

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

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

我很确定一定有办法合理地做到这一点。

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

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

【讨论】:

  • 我喜欢你的风格。感谢您为我的问题提供了很好的答案。
【解决方案5】:

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

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

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

如果单个有效参数测试失败,我们继续进行reallocmemcpy,使用来自包含每个指针及其strlen 的64 条记录的静态数据库中的记录信息,每次将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_ */

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

【讨论】:

  • 回顾所获得的经验。提供一个通用的字符串连接器可能会更好,strdup("")-realloc-memcpy 连接所有正在进行的参数。并且是专门为具有肯定识别倒数的循环处理而设计的。我认为这种方法将有助于更清晰地描绘出在我看来对于不知情的人来说非常模棱两可的事情。
  • 使用counters 生成counts,并使用address locations with counts 在我的书中是相当高级的编码。但话又说回来,我刚刚和C合作。
猜你喜欢
  • 2021-03-17
  • 1970-01-01
  • 1970-01-01
  • 2016-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多