基础回顾之存储类、链接与内存管理

Author Avatar
纸简书生 4月 30, 2017

做技术,越做到后面越觉得基础、底层的才是越难得,也会越有技术含量的。最近做的项目有关音视频、图像处理,涉及到的都死c和c++相关的代码。遇到了问题才发现,c和c++才是通用大法。这两块没学好,那也只有玩一玩应用层、UI这些东西。抽空整理下。

作用域

四种:

  • 多个文件共享的变量
  • 特定文件每个函数共享的变量
  • 某个函数特有的变量
  • 某个函数一个代码块的变量

一个C变量的作用域可以是代码块作用域,函数作用域,文件作用域,基本概念就不说了。

需要注意的几点:

1
2
3
for (int i = 0; i < 10 ; i++) {
printf("%d", i);
}

这里i的作用域是整个for循环。

一个所有函数之外定义的变量就是文件作用域。整个文件可以访问该变量。也叫全局作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "CTest.h"
int a = 4;
int main() {
for (int i = 0; i < 10 ; i++) {
printf("%d", i);
}
printf("%d", a);
return 0;
}
int test() {
a = 6;
printf("%d", a);
return a;
}

链接

  • 外部链接:多个文件使用
  • 内部链接:当前文件使用
  • 空连接:函数代码块使用
1
2
3
4
5
6
int a = 4; // 外部链接
static int b = 5; // 内部链接、文件私有
int main() {
...
return 0;
}

存储时期(变量在内存中生存的时间)

  • 静态存储:程序执行期间一直存储,文件作用域(内部和外部)的变量具有静态存储时期。文件作用域中特别注意用static表明链接类型而并非存储时期。一个使用了static生命的文件作用域变量表示这个变量具有内部作用域。
  • 自动存储:代码块作用域的变量。

5种存储类:自动,寄存器,代码作用域静态,外部静态,内部静态。下面这种表总结了上面的多种情况。

重点说明一下寄存器变量:寄存处变量是存储在CPU的寄存器中,比一般的内存要快很多。但是有一个问题就是寄存器变量的地址是无法获取的。

把变量定义下所有函数之外,就创建了一个外部变量,为了使程序清晰,可以在使用使用外部变量的函数中通过extern来再次声明。如果变量在别的文件定义,使用extern来声明就是必须的。这里想一想OC中的写第三方库的时候,为了让其他模块能够访问某个变量就是通过这种方式来达到目的的。

比如YYWebImageSetter中。
.h

1
2
3
extern NSString *const _YYWebImageFadeAnimationKey;
extern const NSTimeInterval _YYWebImageFadeTime;
extern const NSTimeInterval _YYWebImageProgressiveFadeTime;

.m

1
2
3
NSString *const _YYWebImageFadeAnimationKey = @"YYWebImageFade";
const NSTimeInterval _YYWebImageFadeTime = 0.2;
const NSTimeInterval _YYWebImageProgressiveFadeTime = 0.4;

函数也是具有存储类。函数默认是外部的,可以被其他文件中的函数调用,静态函数只可以在定义它的文件中使用(用static)

使用static可以防止名字的冲突,为文件定义一个私有的变量或者函数。想想平时我们在定义常量的时候,有时候会出现冲突,OC中用static解决的。应该有些同学有印象的。

分配内存

系统自动分配内存:

1
2
float x;
char hello = "hello world";

系统将会预留出存储float或字符串的足够内存空间。也可以指定预留多少内存空间:

1
int plates[100];

声明了100个内存位置,每个位置可以存储一个int值。这里的plate和上面的x、hello变量可以理解为内存的标识符。所以我们可以用x、hello来标识、获取这些内存数据。

malloc&&free&&calloc

用于动态开辟内存。函数void* malloc( size_t size );

函数说明

1
2
3
4
5
6
Allocates size bytes of uninitialized storage.
If allocation succeeds, returns a pointer to the lowest (first) byte in the allocated memory block that is suitably aligned for any object type.
If size is zero, the behavior is implementation defined (null pointer may be returned, or some non-null pointer may be returned that may not be used to access storage, but has to be passed to free).
malloc is thread-safe: it behaves as though only accessing the memory locations visible through its argument, and not any static storage.
A previous call to free or realloc that deallocates a region of memory synchronizes-with a call to malloc that allocates the same or a part of the same region of memory. This synchronization occurs after any access to the memory by the deallocating function and before any access to the memory by malloc. There is a single total order of all allocation and deallocation functions operating on each particular region of memory.

传入所需要的内存字节数,然后malloc找到内存中一个大小适合的快,内存是匿名的,不像上面可以用定义float x;x用于标识。不过返回的是一个开辟内存的第一个字节的地址。虽然没有为它指定名字,但是我们可以通过指针来接受返回的值来访问那块内存。

因为char代表一个字节,所以经常将malloc定义为指向char的指针类型。但是后来又了新的类型——————通用型指针(void *)。这样就可以返回其他类型的了。特别注意如果malloc找不到所需的空间,就会返回空指针。比如传入一个负数就会返回NULl

1
2
double *dbl;
dbl = malloc(30 * sizeof(double));

请求30个double类型值的空间,并把dbl指向该空间的所在的起始位置。然后就可以使用数组那样使用它。简单来讲可以dbl[0],dbl[1]访问

开辟了内存,必定要释放内存。用free释放内存。对应到OC就是那句内存管理的至理名言谁开辟、谁释放(alloc、release)

一次malloc,应该调用一次free。free的参数是malloc返回的地址,释放掉先前分配的内存。不能使用free来释放通过其他形式分配的内存,比如声明一个数组。

例子强调一下free的重要性

1
2
3
4
5
6
7
8
9
10
11
12
13
void memoryTest() {
double array[2000];
for (int i = 0; i< 1000; i++) {
testCopy(array, 2000);
}
}
void testCopy(double arr[], int count) {
double *temp = (double *)malloc(count * sizeof(double));
// No free
}

第一次调用testCopy,创建指针temp,并使用malloc开辟了2000 16,一个32000个字节。当函数终止,temp作为自动变量被销毁,*但是它指向的32000个字节的内存仍然存在,并且无法访问这些内存,因为地址不见了(temp销毁),由于没有调用free,那么就不可以再次使用这些内存。

第二次调用testCopy,又创建一个temp,但是第一次的32000字节的块已经不能再用了。所以malloc不得不再去开辟一块新的内存地址。当函数终止,这块内存同样没有调用free,创建的内存同样不能被访问。

循环1000次,就是32000000个字节。已经有3200万字节在内存中中不能使用。这就是在做iOS开发中MRC时代经常遇到的内存泄漏。

为了说明问题,这里我用xcode测试了一下,把循环次数改为了100000000。

  • 没有加free之前。

  • 加free之后。

对比上面很明显能看到两者的区别。

calloc与malloc最大的区别是calloc会把开辟的全部位置为0。使用方式和malloc类似,同样需要用free释放内存。

1
2
long *test;
test = (long *)calloc(100, sizeof(long));

开辟了可以容乃100个long类型的内存空间。

常见的限定词

const

普通类型前,标识这个值不可变。而用在指针的时候情况就分多种了

因为指针存在,指针不可变还是指向的值不可变。

  • const float *pf(等同于float const * pf):表示pf指向一个常量浮点型数值。但是pf本身的值可以改变。比如它可以指向另一个const值
  • float * const pt:表示pt是一个常量指针,他总是指向同一个地址,但是地址里面的内容可以改变。
  • const float * const pfc:表示pfc是个常量指针,而且指向的地址内容也不能变。

记忆方式,const位于*左边代表指向的值不可变,位于*右边指针不可变