处理char缓冲区

作为C ++程序员,我有时需要使用C语言处理内存缓冲区。例如:

char buffer[512]; sprintf(buffer, "Hello %s!", userName.c_str()); 

或者在Windows中:

 TCHAR buffer[MAX_PATH+1]; // edit: +1 added ::GetCurrentDirectory(sizeof(buffer)/sizeof(TCHAR), &buffer[0]); 

上面的示例是我通常如何创建本地缓冲区(本地堆栈分配的char数组)。 但是,有许多可能的变化,因此我对您对以下问题的答案非常感兴趣:

  • 传递缓冲区为&buffer[0]编程风格比传递buffer更好? (我更喜欢&buffer[0] 。)
  • 是否有一个被认为对堆栈分配缓冲区安全的最大大小?
    • 更新:我的意思是,例如,在Mac,Windows,Linux桌面(非移动设备!)上跨平台桌面应用程序可以被认为是安全的最高值。
  • 静态缓冲区( static char buffer[N]; )更快吗? 是否还有其他论据支持或反对?
  • 使用静态缓冲区时,可以使用返回类型const char * 。 这(通常)是好还是坏? (我确实意识到调用者需要制作自己的副本以避免下一次调用会改变以前的返回值。)
  • 那么使用static char * buffer = new char[N]; ,永远不要删除缓冲区并在每次调用时重复使用它。
  • 我知道在(1)处理大缓冲区或(2)编译时未知最大缓冲区大小时应使用堆分配。 堆栈/堆分配决策中是否还有其他因素?
  • 你喜欢sprintf_smemcpy_s ,……变种吗? (Visual Studio一直试图让我相信这一点很长时间,但我想要第二个意见:p)

我认为您的兴趣主要来自性能视角,因为像vector,string,wstring等解决方案通常甚至可以用于与C API交互。 我建议学习如何使用它们以及如何有效地使用它们。 如果你真的需要它,你甚至可以编写自己的内存分配器,使它们超级快。 如果您确定它们不是您所需要的,那么您仍然没有理由不编写一个简单的包装器来处理这些用于动态情况的RAII的字符串缓冲区。

除此之外:

传递缓冲区为&buffer [0]编程风格比传递缓冲区更好? (我更喜欢和缓冲[0]。)

不。我会认为这种风格稍微不那么有用(这里肯定是主观的),因为你不能用它来传递一个空缓冲区,因此必须对你的样式做例外来将指针传递给可以为null的数组。 但是,如果将数据从std :: vector传递到期望指针的C API,则需要它。

是否有一个被认为对堆栈分配缓冲区安全的最大大小?

这取决于您的平台和编译器设置。 简单的经验法则:如果您对代码是否会溢出堆栈有疑问,请以不可能的方式编写代码。

静态缓冲区(静态字符缓冲区[N];)更快吗? 是否还有其他论据支持或反对?

是的,有一个很大的争论反对它,那就是它使你的function不再重新进入。 如果您的应用程序变为multithreading,则这些函数将不是线程安全的。 即使在单线程应用程序中,在递归调用这些函数时共享相同的缓冲区也会导致问题。

那么使用static char * buffer = new char [N]; 并且永远不要删除缓冲区? (每次调用重用相同的缓冲区。)

我们仍然有同样的问题与重新进入。

我知道在(1)处理大缓冲区或(2)编译时未知最大缓冲区大小时应使用堆分配。 堆栈/堆分配决策中是否还有其他因素?

堆栈展开会破坏堆栈中的对象。 这对于exception安全尤为重要。 因此,即使您在函数内的堆上分配内存,它通常也应该由堆栈上的对象管理(例如:智能指针)。 /// @见RAII。

你喜欢sprintf_s,memcpy_s,……变种吗? (Visual Studio一直试图让我相信这一点很长时间,但我想要第二个意见:p)

MS认为这些函数是更安全的替代品是对的,因为它们没有缓冲区溢出问题,但如果您按原样编写此类代码(不为其他平台编写变体),您的代码将与Microsoft结合,因为它将是非便携。

使用静态缓冲区时,可以使用返回类型const char *。 这(通常)是好还是坏? (我确实意识到调用者需要制作自己的副本以避免下一次调用会改变以前的返回值。)

我几乎在每种情况下都会说,你想使用const char *作为返回指向字符缓冲区指针的函数的返回类型。 对于返回可变char *的函数通常会造成混乱和问题。 它要么将地址返回到全局/静态数据,它首先不应该使用它(参见上面的重新入门),类的本地数据(如果它是一个方法),在这种情况下返回它会破坏类的能力通过允许客户端篡改它来保持不变量(例如:存储的字符串必须始终有效),或者返回由传入函数的指针指定的内存(唯一可以合理地certificate可变字符*的情况)应该退还)。

  1. 如果您想要重新使用代码,请远离静态缓冲区。

  2. 使用snprintf()而不是sprintf(),这样你就可以控制缓冲区溢出。

  3. 你永远不知道在你的通话环境中还剩多少堆栈空间 – 所以没有尺寸在技术上是“安全的”。 大部分时间你都有很多空间。 但那一次会让你好。 我使用经验法则永远不会将数组放在堆栈上。

  4. 让客户拥有缓冲区并将其大小传递给您的函数。 这使得它可以重新进入,并且不会对谁需要管理缓冲区的生命保持模糊性。

  5. 如果你正在处理字符串数据,请仔细检查你的字符串函数,以确保它们终止,特别是当它们到达缓冲区的末尾时。 在处理各种函数的字符串终止时,C库非常不一致。

  • 这取决于你,只是做buffer更简洁,但如果它是一个vector ,你无论如何都需要做&buffer[0]
  • 取决于您的预期平台。
  • 有关系吗? 你确定它是一个问题吗? 编写最容易阅读和维护的代码,然后再担心是否可以将其混淆得更快。 但是对于它的价值,堆栈上的分配非常快(您只需更改堆栈指针值。)
  • 你应该使用std::string 。 如果性能成为问题,您只需返回内部缓冲区即可减少动态分配。 但是std::string返回接口更好更安全,性能是你最后的关注点。
  • 这是内存泄漏。 许多人会认为这没关系,因为操作系统无论如何都是免费的,但我觉得泄漏东西是一种可怕的做法。 使用静态std::vector ,你永远不应该做任何原始分配! 如果你把自己置于可能泄漏的位置(因为它需要明确地完成),那你做错了。
  • 我认为你的(1)和(2)只是掩盖它。 动态分配几乎总是比堆栈分配慢,但您应该更关心哪种情况在您的情况下有意义。
  • 你根本不应该使用它们。 使用std::stringstd::stringstreamstd::copy等。

你有很多问题! 我会尽力回答一对夫妇,给你一个寻找其他人的地方。

是否有一个被认为对堆栈分配缓冲区安全的最大大小?

是的,但堆栈大小本身因您正在使用的平台而异。 看你什么时候担心堆栈大小? 对于一个非常相似的问题。

是静态字符缓冲区[N]; 快点? 是否还有其他论据支持或反对?

static的含义取决于声明缓冲区的位置,但我假设您正在讨论在函数内声明的static ,因此它只初始化一次。 在多次调用的函数中,使用静态缓冲区可能是防止堆栈溢出的好主意,但另外,请记住,分配缓冲区是一种廉价的操作。 此外,在处理多个线程时,静态缓冲区更难处理。

对于大多数其他问题的答案,请参阅大缓冲区与大型静态缓冲区,是否有优势? 。

传递缓冲区为&buffer [0]编程风格比传递缓冲区更好? (我更喜欢和缓冲[0]。)

&buffer[0]使代码对我来说不太可读。 我必须暂停一下,并想知道为什么有人使用它而不是仅仅传递buffer 。 有时您必须使用&buffer[0] (如果bufferstd::vector ),否则,请坚持使用标准C样式。

是否有一个被认为对堆栈分配缓冲区安全的最大大小?

我怀疑有任何实际限制,只要你合理地使用堆栈。 我的开发过程中从来没有遇到任何问题。

如果我正确读取MSDN ,Windows上的线程默认为1MB的堆栈大小。 这是可配置的。 其他平台有其他限制。

是静态字符缓冲区[N]; 快点? 是否还有其他论据支持或反对?

一方面,它可能会减少为堆栈提交内存页面的需要,因此您的应用程序可能会运行得更快。 另一方面,与堆栈相比,转到BSS段或等效可能会减少缓存位置,因此您的应用可能会运行得更慢。 我严重怀疑你是否注意到了这两者之间的区别。

使用static不是线程安全的,而使用堆栈则是。 这对堆栈来说是一个巨大的优势。 (即使你不认为你会被multithreading化,为什么如果将来发生变化会让生活变得更难?)

使用静态缓冲区时,可以让函数返回具有const char *返回类型。 这是一个好主意吗? (我确实意识到调用者需要制作自己的副本以避免下一次调用会改变以前的返回值。)

Const正确性始终是一件好事。

返回指向静态缓冲区的指针容易出错; 稍后调用可能会修改它,另一个线程可能会修改它,等等。改为使用std::string或其他自动分配的内存(即使你的函数需要在内部处理char缓冲区,例如你的GetCurrentDirectory示例。)

那么使用static char * buffer = new char [N]; 并且永远不要删除缓冲区? (每次调用重用相同的缓冲区。)

效率低于仅使用static char buffer[N] ,因为您需要堆分配。

我知道在(1)处理大缓冲区或(2)编译时未知最大缓冲区大小时应使用堆分配。 堆栈/堆分配决策中是否还有其他因素?

见Justin Ardini的回答。

你喜欢sprintf_s,memcpy_s,……变种吗? (Visual Studio一直试图让我相信这一点很长时间,但我想要第二个意见:p)

这是一个有争议的问题。 就个人而言,我认为这些function是个好主意,如果你只是专门针对Windows,那么采用首选的Windows方法并使用这些function会有一些好处。 (如果你以后需要针对Windows以外的其他东西,只要你不依赖于他们的error handling行为,他们就可以很容易地重新实现。)其他人认为安全CRTfunction并不比正确使用它更安全C并引入其他缺点; 维基百科链接到一些针对它们的论点。

如果函数为您提供了一种方法,可以知道它将返回多少个字符,请使用它。 您的示例GetCurrentDirectory就是一个很好的示例:

 DWORD length = ::GetCurrentDirectory(0, NULL); 

然后,您可以使用动态分配的数组(字符串或向量)来获取结果:

 std::vector buffer(length, 0); // assert(buffer.capacity() >= length); // should always be true GetCurrentDirectory(length, &buffer[0]); 

1) buffer&buffer[0]应该是等价的。

2)堆栈大小限制取决于您的平台。 对于大多数简单的函数,我的个人经验法则是动态声明大约~256KB; 这个数字没有真正的押韵或理由,但它只是我自己的约定,它目前在我开发的所有平台的默认堆栈大小范围内。

3)静态缓冲区不是更快或更慢(对于所有意图和目的)。 唯一的区别是访问控制机制。 编译器通常将静态数据放在二进制文件的单独部分中而不是非静态数据中,但是没有涉及明显/显着的性能益处或惩罚。 确切地说,唯一真正的方法是编写程序的方式和时间(因为这里涉及的许多速度方面取决于您的平台/编译器)。

4)如果调用者需要修改它(这会破坏const点),不要返回一个const指针。 当且仅当它们不被设计为被修改时,才将const用于函数参数并返回类型。 如果调用者需要修改该值,最好的办法是让调用者将函数指向预先分配的缓冲区(以及缓冲区大小),并将函数写入该缓冲区。

5)重用缓冲区可能会导致更大缓冲区的性能提升,因为它会绕过每次调用malloc / freenew / delete时所涉及的开销。 但是,如果每次忘记清除缓冲区或者尝试并行运行两个函数副本,则存在意外使用旧数据的风险。 同样,唯一真正知道的方法是尝试两种方式并测量代码运行的时间。

6)堆栈/堆分配的另一个因素是作用域。 堆栈变量在其所在的函数返回时超出范围,但是在堆上动态分配的变量可以安全地返回给调用者,或者在下次调用该函数时访问(la strtok )。

7)我建议不要使用sprintf_smemcpy_s和朋友。 它们不是标准库的一部分,不可移植。 您使用这些函数的次数越多,当您希望在不同的平台上运行代码或使用不同的编译器时,您将获得的额外工作就越多。

  • Buffer或&Buffer [0]完全相同。 你甚至可以写Buffer + 0。 我个人更喜欢写Buffer(我想大多数开发人员也喜欢这个),但这是你个人的选择
  • 最大值取决于堆栈的大小和深度。 如果堆栈中已经有100个函数,则最大值将会更小。 如果可以使用C ++,则可以编写一个缓冲类,动态选择是使用堆栈(对于小尺寸)还是堆(对于大尺寸)。 您将在下面找到代码。
  • 静态缓冲区更快,因为编译器会事先为您保留空间。 堆栈缓冲区也很快。 对于堆栈缓冲区,应用程序只需增加堆栈指针。 对于堆缓冲区,内存管理器必须找到可用空间,向操作系统询问新内存,然后再将其释放,执行一些簿记,……
  • 如果可能,请使用C ++字符串以避免内存泄漏。 否则,呼叫者必须知道他之后是否必须释放内存。 缺点是C ++字符串比静态缓冲区慢(因为它们在堆上分配)。
  • 我不会在全局变量上使用内存分配。 你什么时候删除它? 你能确定没有其他全局变量需要分配的内存(并在分配静态缓冲区之前使用)吗?
  • 无论您使用何种缓冲区,都要尝试隐藏函数调用者的实现。 您可以尝试将缓冲区指针隐藏在类中,让类记住缓冲区是否是动态分配的(因此应该在其析构函数中删除它)。 之后,很容易更改缓冲区的类型,如果只返回一个char指针,则无法执行此操作。
  • 我个人更喜欢普通的sprintf变种,但这可能是因为我还有很多旧的代码,我不想要混合的情况。 在任何情况下,请考虑使用snprintf,您可以在其中传递缓冲区大小。

动态堆栈/堆缓冲区的代码:

 template class DynamicBuffer { private: const static size_t MAXSIZE=1000; public: DynamicBuffer() : m_pointer(0) {if (BUFSIZE>=MAXSIZE) m_pointer = new eltType[BUFSIZE];} ~DynamicBuffer() {if (BUFSIZE>=MAXSIZE) delete[] m_pointer;}; operator eltType * () { return BUFSIZE>=MAXSIZE ? m_pointer : m_buffer; } operator const eltType * () const { return BUFSIZE>=MAXSIZE ? m_pointer : m_buffer; } private: eltType m_buffer[BUFSIZE 

Is passing the buffer as &buffer[0] better programming style than passing buffer? (I prefer &buffer[0].)

这取决于编码标准。 我个人更喜欢: buffer + index而不是&buffer[index]但这是一个品味问题。

Is there a maximum size that is considered safe for stack allocated buffers?

这取决于堆栈大小。 如果缓冲区所需的堆栈数量超过堆栈上的可用数量,则会导致堆栈溢出 。

Is static char buffer[N]; faster? Are there any other arguments for or against it?

是的,它应该更快。 另见这个问题: 声明一个数组中间函数是不好的做法

When using static buffers you can have your function return have the const char * return type. Is this a good idea? (I do realize that the caller will need to make his own copy to avoid that the next call would change the previous return value.)

在这种情况下不确定静态意味着什么,但是:

  1. 如果在栈上声明变量( char buf[100] ):你不应该返回对栈中声明的东西的引用。 它们将在下一个函数调用/声明中被删除(例如,当再次使用堆栈时)。

  2. 如果变量声明为静态static它将使您的代码不可重入。 strtok就是这种情况的一个例子。

What about using static char * buffer = new char[N]; and never deleting the buffer? (Reusing the same buffer each call.)

这是一种可能性,但不推荐,因为它使您的代码不可重入 。

I understand that heap allocation should be used when (1) dealing with large buffers or (2) maximum buffer size is unknown at compile time. Are there any other factors that play in the stack/heap allocation decision?

正在运行的线程的堆栈大小太小,无法适应堆栈声明(前面提到过)。

Should you prefer the sprintf_s, memcpy_s, ... variants? (Visual Studio has been trying to convince me of this for a long time, but I want a second opinion :p )

如果你想让你的代码可移植:不。但在这种情况下,创建便携式宏的工作量非常小:

 // this is not tested - it is just an example #ifdef _WINDOWS #define SPRINTF sprintf_s #else #define SPRINTF sprintf #endif