我有一段由一位非常老的学校程序员编写的代码:-).它就是这样的
typedef struct ts_request { ts_request_buffer_header_def header; char package[1]; } ts_request_def; ts_request_def* request_buffer = malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
程序员基本上正在研究缓冲区溢出概念.我知道代码看起来很狡猾.所以我的问题是:
malloc总是分配连续的内存块吗?因为在这段代码中如果块不连续,代码将失败很长时间
执行free(request_buffer),它将释放malloc分配的所有字节,即sizeof(ts_request_def)+(2*1024*1024),或者只释放结构sizeof(ts_request_def)大小的字节
你是否看到这种方法有任何明显的问题,我需要与老板讨论这个问题,并想指出这种方法有任何漏洞
Chris Young.. 49
回答你的编号.
是.
所有字节.Malloc/free不知道或关心对象的类型,只是大小.
它严格来说是未定义的行为,但是许多实现支持的常见技巧.请参阅下面的其他替代方案
最新的C标准ISO/IEC 9899:1999(非正式的C99)允许灵活的阵列成员.
一个例子是:
int main(void) { struct { size_t x; char a[]; } *p; p = malloc(sizeof *p + 100); if (p) { /* You can now access up to p->a[99] safely */ } }
这个现在标准化的功能允许您避免使用您在问题中描述的常见但非标准的实现扩展.严格地说,使用非灵活的数组成员并超出其边界访问是未定义的行为,但许多实现记录并鼓励它.
此外,gcc允许零长度数组作为扩展.零长度数组在标准C中是非法的,但是在C99为我们提供灵活的数组成员之前,gcc引入了这个功能.
在回复评论时,我将解释为什么下面的代码片段在技术上是未定义的行为.我引用的章节编号参考C99(ISO/IEC 9899:1999)
struct { char arr[1]; } *x; x = malloc(sizeof *x + 1024); x->arr[23] = 42;
首先,6.5.2.1#2显示a [i]与(*((a)+(i)))相同,因此x-> arr [23]等价于(*((x-> arr)+( 23))).现在,6.5.6#8(添加指针和整数)说:
"如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不会产生溢出;否则,行为是未定义的."
出于这个原因,因为x-> arr [23]不在数组中,所以行为是未定义的.您可能仍然认为它没关系,因为malloc()意味着数组现在已经扩展,但事实并非如此.信息性附件J.2(其中列出了未定义行为的示例)通过一个示例提供了进一步的说明:
数组下标超出范围,即使一个对象显然可以使用给定的下标访问(如左边的表达式a [1] [7],给出声明int a [4] [5])(6.5.6).
@Robert S. Barnes:你没错,但物理布局与C标准完全无关.唯一重要的是,当以明确定义的方式访问时,它似乎与程序相邻.同样指出内存可能不连续也是如此,因为它可能跨越多个硅片. (2认同)
TrayMan.. 10
3 - 在结构的末尾分配动态数组是一个非常常见的C技巧.另一种方法是将指针放入结构中,然后单独分配数组,而不是忘记释放它.虽然大小固定为2mb似乎有点不寻常.
回答你的编号.
是.
所有字节.Malloc/free不知道或关心对象的类型,只是大小.
它严格来说是未定义的行为,但是许多实现支持的常见技巧.请参阅下面的其他替代方案
最新的C标准ISO/IEC 9899:1999(非正式的C99)允许灵活的阵列成员.
一个例子是:
int main(void) { struct { size_t x; char a[]; } *p; p = malloc(sizeof *p + 100); if (p) { /* You can now access up to p->a[99] safely */ } }
这个现在标准化的功能允许您避免使用您在问题中描述的常见但非标准的实现扩展.严格地说,使用非灵活的数组成员并超出其边界访问是未定义的行为,但许多实现记录并鼓励它.
此外,gcc允许零长度数组作为扩展.零长度数组在标准C中是非法的,但是在C99为我们提供灵活的数组成员之前,gcc引入了这个功能.
在回复评论时,我将解释为什么下面的代码片段在技术上是未定义的行为.我引用的章节编号参考C99(ISO/IEC 9899:1999)
struct { char arr[1]; } *x; x = malloc(sizeof *x + 1024); x->arr[23] = 42;
首先,6.5.2.1#2显示a [i]与(*((a)+(i)))相同,因此x-> arr [23]等价于(*((x-> arr)+( 23))).现在,6.5.6#8(添加指针和整数)说:
"如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不会产生溢出;否则,行为是未定义的."
出于这个原因,因为x-> arr [23]不在数组中,所以行为是未定义的.您可能仍然认为它没关系,因为malloc()意味着数组现在已经扩展,但事实并非如此.信息性附件J.2(其中列出了未定义行为的示例)通过一个示例提供了进一步的说明:
数组下标超出范围,即使一个对象显然可以使用给定的下标访问(如左边的表达式a [1] [7],给出声明int a [4] [5])(6.5.6).
3 - 在结构的末尾分配动态数组是一个非常常见的C技巧.另一种方法是将指针放入结构中,然后单独分配数组,而不是忘记释放它.虽然大小固定为2mb似乎有点不寻常.
1)是的,如果没有足够大的连续块,malloc将会失败.(malloc失败将返回NULL指针)
2)是的,它会.内部内存分配将跟踪使用该指针值分配的内存量并释放所有内存.
3)这是一种语言黑客攻击,并且对它的使用有点怀疑.它仍然会受到缓冲区溢出的影响,只是可能会让攻击者稍微长一点,以找到导致它的有效负载."保护"的成本也非常高(你真的需要>每个请求缓冲区2mb?).这也很难看,虽然你的老板可能不理解这个论点:)
这是一个标准的C技巧,并不比任何其他缓冲区更危险.
如果你试图向你的老板证明你比"非常老的学校程序员"聪明,那么这个代码就不适合你了.老派不一定不好.似乎"老派"的人对内存管理了解得足够多;)
我不认为现有的答案完全符合这个问题的本质.你说老派程序员正在做这样的事情;
typedef struct ts_request { ts_request_buffer_header_def header; char package[1]; } ts_request_def; ts_request_buffer_def* request_buffer = malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));
我认为他不太可能做到这一点,因为如果这就是他想做的事情,他可以用简化的等效代码来完成它,不需要任何技巧;
typedef struct ts_request { ts_request_buffer_header_def header; char package[2*1024*1024 + 1]; } ts_request_def; ts_request_buffer_def* request_buffer = malloc(sizeof(ts_request_def));
我敢打赌,他真正做的就是这样的事情;
typedef struct ts_request { ts_request_buffer_header_def header; char package[1]; // effectively package[x] } ts_request_def; ts_request_buffer_def* request_buffer = malloc( sizeof(ts_request_def) + x );
他想要实现的是分配具有可变包大小x的请求.使用变量声明数组的大小当然是非法的,所以他通过一个技巧来解决这个问题.看起来他好像知道他在对我做了什么,诀窍很好地朝着C技巧量表的可敬和实际结束.