(译者注:
原文地址http://pfacka.binaryparadise.com/articles/guide-to-advanced-programming-in-C.html
本文是第一部分,翻译的内容为int类型转换,内存分配。)
c语言是系统编程,嵌入式系统以及很多其他应用程序的一种选择。然而似乎对计算机编程不是特别感兴趣,就不会去接触c语言。熟悉c语言的各个方面,了解这个语言的各种细节是一个巨大的挑战。本文试着提供深入的资料来阐述其中的一部分内容。包括:int类型的转换、内存分配、数组的指针转换、显式内联函数、Interpositioning(译者未找到合适的词语翻译) 、向量变化。
int main(int argc, char** argv) {
long i = -1;
if (i < sizeof(i)) {
printf("OK\n");
}
else {
printf("error\n");
}
return 0;
}
导致这样的原因是变量i被转换成了unsigned int类型。所以它的值不再是-1,而是size_t的最大值,这是由sizeof操作符的类型导致的。
uint32_t sum()
{
uint16_t a = 65535;
uint16_t b = 1;
return a+b;
}
char c = 128;
char d = 129;
printf("%d,%d\n",c,d);
size_t computed_size;
if (elem_size && num > SIZE_MAX / elem_size) {
errno = ENOMEM;
err(1, "overflow");
}
computed_size = elem_size*num;
#include
#include
#include
#include
#define VECTOR_OK 0
#define VECTOR_NULL_ERROR 1
#define VECTOR_SIZE_ERROR 2
#define VECTOR_ALLOC_ERROR 3
struct vector {
int *data;
size_t size;
};
int create_vector(struct vector *vc, size_t num) {
if (vc == NULL) {
return VECTOR_NULL_ERROR;
}
vc->data = 0;
vc->size = 0;
/* check for integer and SIZE_MAX overflow */
if (num == 0 || SIZE_MAX / num < sizeof(int)) {
errno = ENOMEM;
return VECTOR_SIZE_ERROR;
}
vc->data = calloc(num, sizeof(int));
/* calloc faild */
if (vc->data == NULL) {
return VECTOR_ALLOC_ERROR;
}
vc->size = num * sizeof(int);
return VECTOR_OK;
}
int grow_vector(struct vector *vc) {
void *newptr = 0;
size_t newsize;
if (vc == NULL) {
return VECTOR_NULL_ERROR;
}
/* check for integer and SIZE_MAX overflow */
if (vc->size == 0 || SIZE_MAX / 2 < vc->size) {
errno = ENOMEM;
return VECTOR_SIZE_ERROR;
}
newsize = vc->size * 2;
newptr = realloc(vc->data, newsize);
/* realloc faild; vector stays intact size was not changed */
if (newptr == NULL) {
return VECTOR_ALLOC_ERROR;
}
/* upon success; update new address and size */
vc->data = newptr;
vc->size = newsize;
return VECTOR_OK;
}
char *ptr = NULL;
/* ... */
void nullfree(void **pptr) {
void *ptr = *pptr;
assert(ptr != NULL)
free(ptr);
*pptr = NULL;
}
2 )通过空指针或者未初始化的指针访问内存。
使用上述规则,你的代码只需要处理空指针或有效的指针。只需要在函数或者代码段开始的时候,检测动态内存的指针是否为空。3 )访问越界的内存
访问越界的内存并不一定都会导致程序崩溃。程序可能继续操作使用错误的数据,产生危险的后果,或者程序可能利用这些操作,进入其他的分支,或者进入执行代码。逐步的人工检测数组边界和动态内存边界,是主要避免这些危险的主要方法。内存边界的信息可以人工跟踪。数组的大小可以用sizeof函数,但是有时候array也会被转换为指针,(例如在函数中,sizeof 会返回指针的大小,而不是数组。)c11标准中的接口Annex k 是边界检测的接口,定义了一系列新库函数,提供了一些简单安全的方法去代替标准库(例如string 和I/O操作) 还有一些开源的方法例如 slibc,但是他的接口没有广泛采用。基于BSD系统(包括Mac OS X)提供了strlcpy,strlcat函数,可以更好的进行字符串操作。对于其他系统可以使用libbsd libraray.很多操作系统提供了接口,控制获取内存区域,保护内存读写例如posix mporst,这些机制主要用于整个内存页的控制。避免内存泄露
内存泄露是由于有些动态内存不在使用了,但是程序没有释放而导致的。所以真正理解分配的内存空间作用域,最重要的是 free函数什么时候调用。但是随着程序复杂性的增强,这个就会变得越来越困难,所以在开始的设计中需要加入内存管理的功能。下面是一些方法去解决这些问题:1)启动的时候申请
将所有需要的堆内存分配防止程序启动的时候可以让内存管理变得简单。在程序结束的时候由操作系统释放(这里的意思是程序结束调用free么?还是程序关闭后系统自己free)。在很多情况下,这个方法是令人满意的,特别是程序批处理输入,然后完成。 2)可变长度的数组
如果你需要一个可变大小的临时存储空间,生命周期只在一个函数中,那么可以考虑使用VLA(可变长度数组)。但是使用它是受限制的,每个函数使用它的空间不能超过百个字节。因为可变长度数组在C99中定义的(C11优化)有自动存储区域,它和其他的自动变量一样有一定的范围。尽管标准没有明确指出,通常会将VLA放在栈空间中。 VLA的最大可以分配的内存空间大小为 SIZE_MAX字节。先要知道目标平台的栈空间大小,我们要谨慎使用,确保不出现栈溢出,或者读取内存段下面的错误数据。 3)人工引用计数
这个技术的背后思想是记录每次分配和失去引用的数目。在每次分配引用的时候计数增加,每次失去引用的时候分配减少。当引用的数目为0的时候,表示内存空间不再使用了,然后进行释放。但是C语言不支持自动析构(实际上,GCC和Clang都支持cleanup扩展)但并不意味着要重写分配操作符,通过人工的调用retain/release来完成计数。 函数。换一个思路,程序中有多个地方会占用或者解除和一块内存空间的关系。即便是使用这个方法,要遵守很多准则来确保不会忘记调用release(导致内存泄露)或过多的调用(提前释放)。但是如果一个内存空间的生命期,是由由外部事件确定,并且程序的结构决定,它会用各种方法来处理内存空间,那么使用这种麻烦的方法也是很值得的。下面代码块是一个简单的引用计数去进行内存管理。
#include
#include
#define MAX_REF_OBJ 100
#define RC_ERROR -1
struct mem_obj_t{
void *ptr;
uint16_t count;
};
static struct mem_obj_t references[MAX_REF_OBJ];
static uint16_t reference_count = 0;
/* create memory object and return handle */
uint16_t create(size_t size){
if (reference_count >= MAX_REF_OBJ)
return RC_ERROR;
if (size){
void *ptr = calloc(1, size);
if (ptr != NULL){
references[reference_count].ptr = ptr;
references[reference_count].count = 0;
return reference_count++;
}
}
return RC_ERROR;
}
/* get memory object and increment reference counter */
void* retain(uint16_t handle){
if(handle < reference_count && handle >= 0){
references[handle].count++;
return references[handle].ptr;
} else {
return NULL;
}
}
/* decrement reference counter */
void release(uint16_t handle){
printf("release\n");
if(handle < reference_count && handle >= 0){
struct mem_obj_t *object = &references[handle];
if (object->count <= 1){
printf("released\n");
free(object->ptr);
reference_count--;
} else {
printf("decremented\n");
object->count--;
}
}
}
void cleanup_release(void** pmem) {
int i;
for(i = 0; i < reference_count; i++) {
if(references[i].ptr == *pmem)
release(i);
}
}
void usage() {
int16_t ref = create(64);
void *mem = retain(ref);
__attribute__((cleanup(cleanup_release), mem));
/* ... */
}
* helper macros */
#define __COMB(X,Y) X##Y
#define COMB(X,Y) __COMB(X,Y)
#define __CLEANUP_RELEASE __attribute__((cleanup(cleanup_release)))
#define retain_auto(REF) retain(REF); int16_t __CLEANUP_RELEASE COMB(__ref,__LINE__) = REF
void cleanup_release(int16_t* phd) {
release(*phd);
}
void usage() {
int16_t ref = create(64);
void *mem = retain_auto(ref);
/* ... */
}
#include
#include
struct pool_t{
void *ptr;
size_t size;
size_t used;
};
/* create memory pool*/
struct pool_t* create_pool(size_t size) {
struct pool_t* pool = calloc(1, sizeof(struct pool_t));
if(pool == NULL)
return NULL;
if (size) {
void *mem = calloc(1, size);
if (mem != NULL) {
pool->ptr = mem;
pool->size = size;
pool->used = 0;
return pool;
}
}
return NULL;
}
/* allocate memory from memory pool */
void* pool_alloc(struct pool_t* pool, size_t size) {
if(pool == NULL)
return NULL;
size_t avail_size = pool->size - pool->used;
if (size && size <= avail_size){
void *mem = pool->ptr + pool->used;
pool->used += size;
return mem;
}
return NULL;
}
/* release memory for whole pool */
void delete_pool(struct pool_t* pool) {
if (pool != NULL) {
free(pool->ptr);
free(pool);
}
}