【问题标题】:Buffer size in CC 中的缓冲区大小
【发布时间】:2009-11-18 23:53:09
【问题描述】:

当在 C 中提供缓冲区大小时,我如何知道还剩多少以及何时需要停止使用内存?

例如,如果我写的函数是这样的:

void ascii_morse (lookuptable *table, char* morse, char* ascii, int morse_size) {

}

在这个应用程序中,我将被传递一个字符串 (ascii),然后我将使用其他函数将每个 ascii 字符转换为莫尔斯码。我面临的问题是如何确保我没有超过缓冲区大小。我什至不知道何时使用缓冲区大小或每次使用时如何减小它。

当然输出将是 morse(所以我将向 morse 添加字符串,但我想我知道该怎么做,只是缓冲区大小对我来说很难理解)

如果您需要更多信息来理解问题,请告诉我,我已尽力解释。

【问题讨论】:

  • 干得好,实际上将这个问题标记为家庭作业。 :)

标签: c buffer


【解决方案1】:

听起来“缓冲区”有些混乱。没有缓冲区。 morse-size 告诉您分配给morse 的内存量(技术上,morse 指向的内存块)。如果 morse-size 为 20,那么您有 20 个字节。这是 19 个字节的可用空间,因为字符串以空字节终止。您可以将morse-size 视为“字符串的最大长度加一”。

您需要检查morse-size 以确保您没有向morse 写入超出其可以容纳的字节数。 morse 只不过是一个指向内存中一个点的数字。不是一个范围,而是一个点。之后分配给morse 的内容。如果您在morse 中输入的内容更多,则可能会覆盖其他人的记忆。 C 不会为你检查这个,这是最高性能的代价。

这就像如果你去剧院,引座员告诉你,“你可以有 A3 和接下来的 5 个座位​​”然后离开。你必须有礼貌,不要坐6个座位,别人给了A8。

valgrind 之类的工具对于发现 C 中的内存错误并保持理智非常有用。

C 中的字符串不是很有趣吗?欢迎来到整个计算世界中最大的错误根源。

【讨论】:

  • 哇。 “没有缓冲”让我变得形而上学,就像矩阵的“没有勺子”:-)
  • "将目标字符串放在源之前是错误的形式。" ???你认为标准库函数是“坏形式”吗?
  • @pmg 既然你问了,是的。 :) 我不是原生 C 程序员,所以我的风格不是原生 C。标准 C 库约定是在 30 多年前制定的,当时计算世界是一个非常不同的地方。它们非常陈旧,与宇宙的其他部分格格不入。我想这取决于您周围的代码有多少遵循 C 约定。比如,如果你对 glib 投入了大量资金,你可能应该坚持使用 dest, source。
  • 虽然我不同意你对 dest-first 的看法(遵循与dest = source 相同的顺序是一致的),但你坚定地支持它。
  • @Schwern:好点,谢谢你的回答。我在库约定中编写代码,所以我把目的地放在第一位,我已经习惯了。如果我要编写一个“ascii2morse”函数,它将与 strcat、strcpy 和 &c 并排,因此遵循相同的约定会更有意义。
【解决方案2】:
void ascii-morse (lookuptable *table, char* morse, char* ascii, int morse-size)

从上面的原型来看,你已经传入了输出缓冲区的大小。

ascii 无疑将是一个空终止的字符串,morse 将是输出缓冲区:morse_size不是morse-size,因为这不是一个有效的标识符) 将是您允许写入的字符数。

伪代码将类似于:

set apointer to start of ascii, mpointer to start of morse.
while apointer not at end of ascii:
    get translation from lookuptable, using the character at apointer.
    if length of translation is greater than morse_size:
        return an error.
    store translation to mpointer.
    add 1 to apointer.
    add length of translation to mpointer.
    subtract length of translation from morse_size.
if morse_size is zero:
    return an error.
store string terminator to mpointer.

您必须将其转换为 C 并实现查找功能,但这应该是一个好的开始。

指针用于从相关字符串中提取和插入。对于每个字符,您基本上检查输出缓冲区中是否有足够的空间来添加莫尔斯电码段。最后,您还需要检查字符串终止符'\0' 是否有足够的空间;

您检查是否有足够空间的方式是通过每次循环将morse_size 变量减去您添加到morse 的字符串的长度。这样,morse_size 将始终是缓冲区中剩余的大小供您使用。

【讨论】:

  • 哦,我想我明白了,但还有一件事,所以我将一个字符一个字符地添加到莫尔斯,因为这就是我将 ascii 翻译成莫尔斯的方式。每次我想添加一个字符时如何检查是否有足够的内存?我的意思是,如果我通过了“hello world”缓冲区,只够“hel”了,我怎么知道我应该停在那里?每次我检查一个字符或类似的东西时,我不应该减小缓冲区大小吗?
  • 见最后一段。您不断地通过您添加的莫尔斯电码段的长度来减少 morse_size 变量。当你得到一个 3 字符的莫尔斯电码段并且 morse_size 只有两个(例如)时,你就有了一个错误条件。最后的字符串终止符也是如此。
【解决方案3】:

您需要将缓冲区大小与指针一起传递。

int
ascii_to_morse(lookuptable *table,
               char* morse, int morse_size,
               char* ascii);

缓冲区大小不一定与字符串的当前长度相同(可以使用 strlen 找到)。

上面给出的函数将读取 ascii 字符串(不需要知道缓冲区大小,因此不会传递)并写入 morse 指向的缓冲区,大小为 morse_size。它返回写入的字节数(不包括 null)。

编辑:这里是这个函数的一个实现,虽然它无法为摩尔斯电码使用正确的值,但它显示了如何管理缓冲区:

typedef void lookuptable; // we ignore this parameter below anyway
// but using void lets us compile the code

int
ascii_to_morse(lookuptable *table,
               char* morse, int morse_size,
               char* ascii)
{
  if (!ascii || !morse || morse_size < 1) { // check preconditions
    return 0; // and handle it as appropriate
    // you may wish to do something else if morse is null
    // such as calculate the needed size
  }
  int remaining_size = morse_size;
  while (*ascii) { // false when *ascii == '\0'
    char* mc_for_letter = ".-"; //BUG: wrong morse code value
    ++ascii;
    int len = strlen(mc_for_letter);
    if (remaining_size <= len) { // not enough room
      // 'or equal' because we must write a '\0' still
      break;
    }
    strcpy(morse, mc_for_letter);
    morse += len; // keep morse always pointing at the next location to write
    remaining_size -= len;
  }
  *morse = '\0';
  return morse_size - remaining_size;
}

// test the above function:
int main() {
  char buf[10];
  printf("%d \"%s\"\n", ascii_to_morse(0, buf, sizeof buf, "aaa"), buf);
  printf("%d \"%s\"\n", ascii_to_morse(0, buf, sizeof buf, "a"), buf);
  printf("%d \"%s\"\n", ascii_to_morse(0, buf, sizeof buf, "aaaaa"), buf);
  return 0;
}

【讨论】:

  • 例如,如果我传递了一个 10 的缓冲区大小并且字符串是“hello world”,我是否会说缓冲区大小——每次我阅读这些字符时?
  • 不,因为 C 中的字符串以空字节终止,您可以使用 strlen 来获取长度。担心缓冲区大小适用于您正在写入的字符串。
  • morse_size 是结果的大小。您必须计算放入“morse”的字符数,并在达到 morse_size -1 时停止(因为您想为 nul 终止符保留最后一个字符)。当您从 'ascii' 中读取字符时,您只需读取到最后,这将是 nul 字符。
  • 为家庭作业问题提供代码被认为是错误的形式。 OP 无论如何都不能使用它,因为这些网站无疑会受到监控,从长远来看,它对使用它的人没什么好处。
  • 我已经解决了整个问题,我只需要缓冲区方面的帮助 :) 所以这个例子很棒 :) 我将尝试实施并在需要时提出更多问题,但现在谢谢你!
【解决方案4】:

不能仅从指针推断缓冲区大小。它需要作为参数传递,或者以某种方式知道(如从 DEFINE 值或其他常量)或隐式知道......(后一种隐式方法是“危险的”,因为如果大小以某种方式发生了变化,但这样的变化是没有反映在使用缓冲区的地方...)

另外,更典型的是,在输入缓冲区(函数将从中读取的缓冲区)的情况下,缓冲区的结尾可以用特殊字符或此类字符的序列来标记。

【讨论】:

  • 它通过了,但是我如何使用它,我是每次读取一个字符时减少它还是什么?
  • 您使用显式传递的缓冲区大小的方式可能会有所不同。您减少添加到缓冲区的字符数的建议是可行的。另一种方法是在添加任何内容之前计算缓冲区中的最大插入点,并检查该指针是否会保持小于当前插入指针。
  • 我一直想知道为什么不能仅从指针确定缓冲区大小。它必须被某些东西知道,否则 free() 将不起作用。有没有技术原因为什么不能有一个“intallocated_to(void *ptr)”函数?它只是标准 C API 中的漏洞之一吗?
  • @Schwern,是的,确实,此信息存在于某处/不知何故,以免管理堆变得一团糟;-) 但是,C 和 C++ 标准在这方面没有提供任何要求,并且因此,如果保留大小信息的方式是特定于实现的,因此对 [谨慎和明智的] 程序员是禁止的。此外,这个问题并不特定于动态分配的块,我们可以想象一个应用程序在静态内存区域中传递一些指针,即使编译器也不知道。而且,即使使用动态分配的块,程序也可以将指针传递给...
  • ... 在字符串中间的某个位置,和/或期望被调用的函数不会使用超过特定长度,即使 bloc 本身更大。简而言之,可能没有这么人为的方式表明明确传递长度是个好主意!
【解决方案5】:

一种可能的(慢速)解决方案是允许函数处理 NULL 缓冲区指针并返回所需的缓冲区大小。然后用适当大小的缓冲区第二次调用它

【讨论】:

    【解决方案6】:

    另一种解决方案不是传入要写入的预分配目标字符串,而是您的函数执行分配并返回指向该目标的指针。这更加安全,因为调用者不必猜测您的函数需要多少内存。

    char *ascii2morse(const char *ascii, lookuptable *table)
    

    您仍然需要为摩尔斯电码分配足够的内存。由于摩尔斯电码不是固定长度的,因此有两种策略。第一个是简单地计算出给定长度字符串(最长莫尔斯序列 * ascii 中的字符数)所需的最大可能内存并分配它。这可能看起来很浪费,但无论如何调用者都必须为您的原始计划做些什么。

    另一种方法是使用realloc 来根据需要不断增长字符串。您计算出编码下一个字符需要多少字节,重新分配那么多字节并将其附加到字符串中。这可能会更慢,现在内存分配器非常复杂,但它会根据您的需要使用尽可能多的内存。

    既避免了用户必须预先分配未知数量的内存的陷阱,又消除了不必要的“用户没有分配足够的内存”错误情况。

    如果您真的想节省内存,我会将摩尔斯电码中的每个点/破折号存储为 2 位而不是 8 位。你有三个“单词”,短字母和长字母。这至少是 2 位空间。

    【讨论】:

    • 但是现在您与 API 使用者签订了释放该内存的合同。可行,但丑陋且相当危险。
    • @rpj 调用者不能释放它吗?
    猜你喜欢
    • 2011-11-26
    • 2017-04-07
    • 1970-01-01
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多