作为一名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]
。)static char buffer[N];
)更快吗?还有其他论据支持或反对吗?const char *
。这(通常)是好还是坏? (我确实知道调用者需要制作自己的副本,以避免下一次调用会改变之前的返回值。)static char * buffer = new char[N];
,永远不要删除缓冲区并在每次调用时重复使用。sprintf_s
,memcpy_s
,...变体? (Visual Studio一直试图让我相信这一点很长时间,但我想要第二个意见:p)答案 0 :(得分:8)
如果您想要重新使用代码,请远离静态缓冲区。
使用snprintf()代替sprintf(),这样就可以控制缓冲区溢出。
您永远不知道在通话环境中剩余多少堆栈空间 - 所以没有尺寸在技术上是“安全的”。大部分时间你都有很多空间。但那一次会让你好。我使用经验法则从不将数组放在堆栈上。
让客户拥有缓冲区并将其大小传递给您的函数。这使得它可以重新进入,并且对于谁需要管理缓冲区的生命没有任何含糊之处。
如果您正在处理字符串数据,请仔细检查字符串函数,以确保它们在缓冲区结束时终止。在处理各种函数的字符串终止时,C库非常不一致。
答案 1 :(得分:4)
我认为您的兴趣主要来自性能视角,因为像vector,string,wstring等解决方案通常甚至可以用于与C API交互。我建议学习如何使用它们以及如何有效地使用它们。如果你真的需要它,你甚至可以编写自己的内存分配器,使它们超级快。如果您确定它们不是您需要的,那么您仍然没有理由不编写一个简单的包装器来处理动态情况下使用RAII的字符串缓冲区。
解决这个问题:
将缓冲区作为& buffer [0]传递 编程风格比传递更好 缓冲? (我更喜欢& buffer [0]。)
没有。我认为这种风格稍微不那么有用(不可否认在这里是主观的),因为你不能用它来传递一个空缓冲区,因此必须对你的样式做例外来将指针传递给可以为null的数组。但是,如果将数据从std :: vector传递到期望指针的C API,则需要它。
是否有最大尺寸 认为堆栈分配安全 缓冲器?
这取决于您的平台和编译器设置。简单的经验法则:如果您对代码是否会溢出堆栈有疑问,请以不能的方式编写代码。
是静态缓冲区(静态字符 缓冲[N];)更快?有没有 支持或反对的其他论据?
是的,有一个很大的争论反对它,那就是它使你的功能不再可重入。如果您的应用程序变为多线程,则这些函数将不是线程安全的。即使在单线程应用程序中,在递归调用这些函数时共享相同的缓冲区也会导致问题。
使用静态char *缓冲区怎么样? =新字符[N];并且永远不要删除缓冲区? (每个重用相同的缓冲区 调用。)
我们在重新入职方面仍有同样的问题。
我理解堆分配 应该在(1)处理时使用 大缓冲区或(2)最大缓冲区 大小在编译时是未知的。是 还有其他因素在起作用 堆栈/堆分配决策?
堆栈展开会破坏堆栈上的对象。这对于异常安全尤为重要。因此,即使您在函数内的堆上分配内存,它通常也应该由堆栈上的对象管理(例如:智能指针)。 /// @见RAII。
MS是正确的,这些函数是更安全的替代品,因为它们没有缓冲区溢出问题,但如果您按原样编写此类代码(不为其他平台编写变体),您的代码将与Microsoft结合,因为它将是不便携的。您是否更喜欢sprintf_s, memcpy_s,...变种? (视觉工作室 一直试图让我相信这一点 很长一段时间,但我想要一秒钟 意见:p)
使用静态缓冲区时可以使用 返回类型const char *。这是 (一般)好主意还是坏主意? (一世 确实认识到呼叫者需要 制作自己的副本以避免这种情况 下次通话会改变之前的 返回值。)
我几乎在每种情况下都会说,你想使用const char *作为返回指向字符缓冲区的指针的函数的返回类型。对于返回可变char *的函数,通常会造成混乱和问题。它要么将地址返回到全局/静态数据,它首先不应该使用它(参见上面的重新入门),类的本地数据(如果它是一个方法),在这种情况下返回它会破坏类的能力通过允许客户端篡改它来保持不变量(例如:存储的字符串必须始终有效),或者返回由传入函数的指针指定的内存(唯一可以合理地论证可变字符*的情况)应该退回。)
答案 2 :(得分:3)
答案 3 :(得分:3)
你有很多问题!我会尽我所能回答一对,给你一个寻找其他人的地方。
堆栈分配缓冲区的最大大小是否安全?
是的,但堆栈大小本身因您正在使用的平台而异。有关非常类似的问题,请参阅When do you worry about stack size?。
是静态字符缓冲区[N];快点?是 或者还有其他任何论据 反对吗?
static的含义取决于声明缓冲区的位置,但我假设您正在讨论函数内部声明的static
,因此它只初始化一次。在多次调用的函数中,使用静态缓冲区可能是防止堆栈溢出的好主意,但另外,请记住,分配缓冲区是一种廉价的操作。此外,在处理多个线程时,静态缓冲区更难处理。
要获得大多数其他问题的答案,请参阅Large buffers vs Large static buffers, is there an advantage?。
答案 4 :(得分:1)
传递缓冲区和& buffer [0]编程风格比传递缓冲区更好吗? (我更喜欢& buffer [0]。)
&buffer[0]
使代码对我来说不太可读。我必须暂停一下,并想知道为什么有人使用它而不只是传递buffer
。有时您必须使用&buffer[0]
(如果buffer
是std::vector
),否则,请坚持使用标准C样式。
是否有一个被认为对堆栈分配缓冲区安全的最大大小?
我怀疑有任何实际限制,只要你合理地使用堆栈。我的发展过程中从来没有遇到任何问题。
如果我正确读取MSDN,Windows上的线程默认为1MB的堆栈大小。这是可配置的。其他平台有其他限制。
是静态字符缓冲区[N];快点?是否还有其他论据支持或反对?
一方面,它可能会减少为堆栈提交内存页面的需要,因此您的应用程序可能会运行得更快。另一方面,与堆栈相比,转到BSS segment或等效可能会减少缓存位置,因此您的应用可能会运行得更慢。我非常怀疑你是否注意到了这种差异。
使用static
不是线程安全的,而使用堆栈是。这对堆栈来说是一个巨大的优势。 (即使你不认为你会被多线程化,为什么如果将来发生变化会让生活变得更难?)
使用静态缓冲区时,可以让函数返回具有const char *返回类型。这是一个好主意吗? (我确实意识到调用者需要自己创建副本以避免下一次调用会改变之前的返回值。)
Const correctness总是一件好事。
返回指向静态缓冲区的指针容易出错;稍后调用可能会修改它,另一个线程可能会修改它,等等。改为使用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)
这是一个有争议的问题。就个人而言,我认为这些功能是一个好主意,如果你专门针对Windows,那么采用首选的Windows方法并使用这些功能会有一些好处。 (如果你以后需要针对Windows以外的其他东西,只要你不依赖于他们的错误处理行为,他们就可以相当简单地重新实现。)其他人认为安全CRT功能并不比正确使用它更安全C并引入其他缺点; Wikipedia links反对他们的一些论点。
答案 5 :(得分:1)
如果某个函数为您提供了一种方法,可以知道它将返回多少个字符,请使用它。您的示例GetCurrentDirectory就是一个很好的示例:
DWORD length = ::GetCurrentDirectory(0, NULL);
然后您可以使用动态分配的数组(字符串或向量)来获得结果:
std::vector<TCHAR> buffer(length, 0);
// assert(buffer.capacity() >= length); // should always be true
GetCurrentDirectory(length, &buffer[0]);
答案 6 :(得分:1)
1)buffer
和&buffer[0]
应该是等效的。
2)堆栈大小限制取决于您的平台。对于大多数简单的函数,我的个人经验法则是动态声明大约~256KB;但是,这个数字没有真正的押韵或理由,它只是我自己的约定,它目前在我开发的所有平台的默认堆栈大小内。
3)静态缓冲区不会更快或更慢(对于所有意图和目的)。唯一的区别是访问控制机制。编译器通常将静态数据放在二进制文件的单独部分中而不是非静态数据中,但是没有涉及明显/显着的性能益处或惩罚。确切地说,唯一真正的方法是编写程序的方式和时间(因为这里涉及的许多速度方面取决于您的平台/编译器)。
4)如果调用者需要修改const
,则不要返回const
指针(这会使const
点失效)。使用malloc
作为函数参数并返回类型,当且仅当它们不是为了修改而设计时。如果调用者需要修改该值,那么最好的办法是让调用者向函数传递指向预先分配的缓冲区的指针(以及缓冲区大小),并将函数写入该缓冲区。
5)由于绕过调用free
/ new
或delete
/ sprintf_s
所涉及的开销,重用缓冲区可能会导致较大缓冲区的性能提升每一次。但是,如果您每次忘记清除缓冲区或者尝试并行运行两个函数副本,则存在意外使用旧数据的风险。同样,唯一真正知道的方法是尝试两种方式并测量代码运行的时间。
6)堆栈/堆分配的另一个因素是作用域。堆栈变量在其所在的函数返回时超出范围,但是在堆上动态分配的变量可以安全地返回给调用者,或者在下次调用函数时访问(la strtok)
7)我建议不要使用memcpy_s
,{{1}}和朋友。它们不是标准库的一部分,不可移植。您使用这些函数的次数越多,当您希望在不同的平台上运行代码或使用不同的编译器时,您将获得更多的额外工作。
答案 7 :(得分:0)
动态堆栈/堆缓冲区的代码:
template<size_t BUFSIZE,typename eltType=char>
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<MAXSIZE?BUFSIZE:1];
eltType *m_pointer;
};
答案 8 :(得分:0)
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?
这取决于堆栈大小。如果缓冲区所需的堆栈数量超过堆栈上的可用数量,则会产生stack-overflow。
Is static char buffer[N]; faster? Are there any other arguments for or against it?
是的,它应该更快。 另见这个问题:Is it bad practice to declare an array mid-function
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.)
在这种情况下不确定静态意味着什么,但是:
如果在堆栈上声明变量(char buf[100]
):
你应该不返回对堆栈中声明的东西的引用。它们将在下一个函数调用/声明中被删除(例如,当再次使用堆栈时)。
如果变量声明为静态static
,它将使您的代码不可重入。在这种情况下,strtok就是一个例子。
What about using static char * buffer = new char[N]; and never deleting the buffer? (Reusing the same buffer each call.)
这是一种可能性,但不推荐,因为它会使您的代码non-reentrant。
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