C day13内存管理,存储类别,链接(二)

外部链接的静态变量(外部变量)

外部链接的静态存储类别 被称为 外部存储类别

直接把声明放在所有函数外部就行了,但是如果为了强调(说明不是必需的)它是外部变量,可以在函数中再用extern声明一次,而且根本都不用写数组大小,因为外部变量有文件作用域,整个文件都可以看到它

但是如果要用别的源代码文件中的外部变量,则必须在自己的所有函数外面用extern声明一下。
在这里插入图片描述

在这里插入图片描述

用extern的声明不会分配空间,因为他只是引用式声明,不是定义式声明

在这里插入图片描述在这里插入图片描述
外部变量只会被初始化一次,只能用定义式声明中初始化
在这里插入图片描述

示例

#include <stdio.h>
int units = 0;
void critic(void);
int main()
{
    extern int units;//再强调一下

    printf("How many pounds to a firkin of butter?\n");
    scanf("%d", &units);
    while(units != 56)
        critic();
    printf("You must have looked it up!\n");
    return 0;
}

void critic(void)
{
    printf("No luck, my friend. Try again!\n");
    scanf("%d", &units);
}
How many pounds to a firkin of butter?
54
No luck, my friend. Try again!
98
No luck, my friend. Try again!
56
You must have looked it up!

在这里插入图片描述在这里插入图片描述

内部链接的静态变量

6个存储类别说明符

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
静态存储期对立面是自动存储期
自动变量一定是块作用域的局部变量
静态变量可以是无连接的局部变量,也可以是有内部链接和外部链接的全局变量。

示例

实际上一般程序,包括这个示例,根本不用设计外部变量,使用多个文件这么复杂

#include <stdio.h>
void report_count();
void accumulate(int k);//虽然这个函数的定义不在本文件中,但用到了就必须声明
int count = 0;//文件作用域,外部链接

int main()
{
    int value;//自动变量
    register int i;//寄存器变量

    printf("Enter a positive integer:(0 to quit)\n");
    while(scanf("%d", &value)==1 && value>0)
    {
        ++count;
        for(i=value;i>=0;i--)
            accumulate(i);
        printf("Enter a positive integer:(0 to quit)\n");
    }
    report_count();

    return 0;
}

void report_count()
{
    printf("Loop executed %d times.\n", count);
}

另一个.c文件

#include <stdio.h>
extern int count;//说明使用其他文件的变量
static int total = 0;//静态内部链接变量
void accumulate(int k);

void accumulate(int k)
{
    static int subtotal = 0;//静态局部变量,
    if(k<=0)
    {
        printf("loop cycle: %d\n", count);
        printf("subtotal:%d;total:%d\n", subtotal, total);
        subtotal = 0;
    }
    else{
        subtotal += k;
        total += k;
    }
}
Enter a positive integer:(0 to quit)
2
loop cycle: 1
subtotal:3;total:3
Enter a positive integer:(0 to quit)
3
loop cycle: 2
subtotal:6;total:9
Enter a positive integer:(0 to quit)
0
Loop executed 2 times.

函数的存储类别

除了变量外,函数也是有存储类别的,我们之前写的大多数函数都是外部函数,即可以被其它文件访问,如果你在文件A中使用文件B中定义的外部函数,则最好用extern显式写一下函数原型,也是为了明确意图,不是必需在函数原型中加个extern,但是函数原型是必须的。

如果希望函数只在本文件中可见,被本文件私有,则在其原型和定义中加个static。这样做还可以避免名称冲突,其他文件中可以有同名函数。
在这里插入图片描述

如何选择存储类别 保护性程序设计

在这里插入图片描述在这里插入图片描述

静态变量示例 随机数函数

随机数函数用于生成伪随机数,C库提供了一个rand函数,和一个可移植的版本(不同系统生成相同的随机数)。

生成伪随机数有很多种算法,这里举个栗子,多文件编译
文件1

#include <stdio.h>
extern unsigned int rand0(void);

int main()
{
    int count;

    for(count=0;count<5;count++)
    {
        printf("%d\n", rand0());
    }
    return 0;
}

文件2

static unsigned long int next = 1;
unsigned int rand0()
{
	//生成随机数的魔术公式
    next = next * 1103515245 + 12345;
    return (unsigned int)(next / 65536) % 32768;
}
16838
5758
10113
17515
31051

再运行一次,得到的输出相同,因为种子一样,所以是伪随机

16838
5758
10113
17515
31051

那么如何让每次运次输出不同的一组伪随机数呢?这就需要改变种子,所以需要另一个函数专门改变种子,那么种子变量next就要是生成随机数的rand0和重置种子函数共享的变量,可以设置为内部链接的静态变量

#include <stdio.h>
extern unsigned int rand0(void);
extern void srand1(unsigned int x);
int main()
{
    int count;
    unsigned seed;

    printf("Please enter your choice for seed.\n");
    while(scanf("%u", &seed)==1)
    {
        srand1(seed);//重置种子
        for(count=0;count<5;count++)
            printf("%d\n", rand0());
        printf("Please enter next seed (q to quit):\n");
    }
    printf("Done!\n");

    return 0;
}
static unsigned long int next = 1;
unsigned int rand0()
{
    next = next * 1103515245 + 12345;
    return (unsigned int)(next / 65536) % 32768;
}

void srand1(unsigned int seed)
{
    next = seed;
}
Please enter your choice for seed.
1
16838
5758
10113
17515
31051
Please enter next seed (q to quit):
2
908
22817
10239
12914
25837
Please enter next seed (q to quit):
3
17747
7107
10365
8312
20622
Please enter next seed (q to quit):
4
1817
24166
10491
3711
15407
Please enter next seed (q to quit):
0
0
21468
9988
22117
3498
Please enter next seed (q to quit):
q
Done!

还可以通过系统时间来重置种子,由于时间是可变的,所以种子也一直在变化,从而得到不同的伪随机数

time()的返回值类型是time_t,所以强制转换,而time()的接受参数是time_t类型对象的地址,这里传入空指针

只改动文件1

#include <stdio.h>
#include <time.h>
extern unsigned int rand0(void);
extern void srand1(unsigned int x);
int main()
{
    int count;
    srand1((unsigned int)time(0));//重置种子,time()的返回值类型是time_t,所以强制转换,而time()的接受参数是time_t类型对象的地址,这里传入空指针
    for(count=0;count<5;count++)
        printf("%d\n", rand0());
    printf("Done!\n");
    return 0;
}

两次输出不同值

13266
17357
6273
7898
5364
Done!
1879
8394
7656
22818
13538
Done!

静态变量示例2 掷骰子 (调试大法好啊)

rand() srand()函数的原型在stdlib.h中
今天状态不好,这个程序看了很久,它非常讲究模块化,把每个程序的功能划分的很清楚,比如输入由main做获取,但是输入的合法性还是由扔骰子的函数自己判断,输入不合理就输出不合理的负值就行。

函数里的continue, break等跳转非常好,我发现我对break和continue掌握的不是很熟。他们都不对if生效,而是对包裹它们的第一个while有效。

main()函数

#include <stdio.h>
#include <stdlib.h>//srand()原型
#include <time.h>
#include "diceroll.h"
extern int rollem(int sides);
extern int roll_count;

int main()
{
    int dice, roll;
    int sides;
    int status;

    srand((unsigned int)time(0));//随机种子
    printf("Enter the number of sides per die, 0 to stop.\n");

    //while循环获取骰子的面数和掷骰子次数,并掷骰子,求出多次掷骰子的点数之和并输出
    while(scanf("%d", &sides) == 1 && sides>0)
    {
        //if(sides<2)
            //{puts("At least 2 sides are needed.");
           // printf("Enter the number of sides per die, 0 to stop.\n");
           // continue;}
        printf("How many dice?\n");
        if((status = scanf("%d", &dice))!=1)//status!=1则输入的不是整数,比如字母
        {
            if(status == EOF)//CTRL+ENTER
                break;//不是跳出if,是跳出while
            else
            {
                printf("You should have entered an integer.");
                printf("Let's begin again.\n");
                //把刚才的错误输入读取出来,不要存在缓冲区中,以免影响后续
                while(getchar()!='\n')
                    continue;
                printf("How many sides? Enter 0 to stop.\n");
                continue;//continue对if无效,使得程序跳转到while处

            }
        }

        //  sides dice的正确性也由roll_n_dice判断
        roll = roll_n_dice(dice, sides);
        printf("You have rolled a %d using %d %d-sided dice.\n", roll, dice, sides);
        printf("How many sides? Enter 0 to stop.\n");
    }

    printf("The rollem() function was called %d times.\n", roll_count);
    return 0;
}

diceroll.c

#include <stdlib.h>//rand()函数的原型
#include "diceroll.h"//双引号告诉编译器去本地找头文件,而不是去C标准库
#include <stdio.h>
int roll_count = 0;//外部链接

static int rollem(int sides)
{
    int roll;

    //rand()函数生成0-INT_MAX的随机数
    //rand() % sides的结果位于0到(sides-1)
    roll = rand() % sides + 1;//结果位于1到sides
    ++roll_count;
    return roll;
}

int roll_n_dice(int dice, int sides)
{
    int sum=0;
    int d, r;

    if(sides<2)
    {
        puts("At least 2 sides are needed.");
        return -2;
    }
    if(dice<1)
    {
        puts("At least 1 die.");
        return -1;
    }
    for(d=0;d<dice;d++){
        r = rollem(sides);
        printf("You throw a %d.\n", r);
        sum += r;}

    return sum;
}

diceroll.h
这里是为了演示自己写的头文件的用法,双引号

int roll_n_dice(int dice, int sides);
Enter the number of sides per die, 0 to stop.
6
How many dice?
1
You throw a 5.
You have rolled a 5 using 1 6-sided dice.
How many sides? Enter 0 to stop.
6
How many dice?
3
You throw a 2.
You throw a 4.
You throw a 5.
You have rolled a 11 using 3 6-sided dice.
How many sides? Enter 0 to stop.
8
How many dice?
2
You throw a 1.
You throw a 7.
You have rolled a 8 using 2 8-sided dice.
How many sides? Enter 0 to stop.
0
The rollem() function was called 6 times.

用库函数动态分配和释放内存 malloc() calloc() free()

在这里插入图片描述
除了静态内存和自动内存之外,C还可以通过另一种方式在程序运行时分配内存(自动内存也是运行时分配)。
这种方式就是使用三个库函数,malloc(),calloc(),free()
malloc(): memory allocation

malloc()函数

接受一个参数:需要的内存字节数

malloc函数自动在内存中寻找匿名的空闲内存块(即没有标识符指定它?或者说它不是对象?),malloc函数也不会为这个内存命名。

返回值通常被定义为指向char的指针,是分配的内存块的首字节地址,把这个地址赋给指针变量,通过指针去访问这片内存。但是ANSI C 标准开始,C规定malloc的返回值是指向void的指针,即一种通用指针,它可以被强制转换为各种类型的指针,比如指向数组的,指向结构的。

如果malloc分配失败,返回空指针。

在这里插入图片描述

三种方法创建数组(动态数组可以节省内存)

在这里插入图片描述
第一种:声明时加static则占用静态内存;否则占用自动内存。
第二种:变长数组只能在自动内存中创建。
第三种:不通过声明数组,而是通过声明指针,但是可以对指针用个static,就占用静态内存,否则也是占用自动内存。

第一种方法创建普通数组(动态数组的对立面不叫静态数组哈,静态指的是占用的内存始终不变),第2,3种方法创建动态数组,即在运行时选择数组的大小和分配内存。

使用动态数组更加灵活,有的时候可能只会用到100个元素,有时候又要1000个,以前的普通数组要求我们必须创建长度1000的数组,这样无疑浪费了空间。

动态内存分配和变长数组的区别

二者的功能比较重合,都可以用于创建运行时才确定大小的数组。

区别:

  • 变长数组是自动存储类型。所以程序离开定义它的块后,它占用的内存就被自动释放了。不需要使用free()。但是malloc创建的数组必须用free释放。
  • 变长数组是块作用域的,所以程序只能在定义它的块访问存储他的那块内存;但是malloc数组不会被局限于一个块,比如不会被局限于一个函数内部。比如,如果被调函数创建了一个malloc数组,只要把指针返回给主调函数,主调函数就也能访问那块动态内存。
  • 多维数组的创建中,变长数组比malloc好使。
    如下:
    指针表示法中,int (*p2)[6]中, ∗ * 被圆括号括起来,所以p2是一个指针,指向数组的指针,所以这是声明了一个二维数组。

但是还是变长数组的声明看起来简单易懂呀
在这里插入图片描述

malloc()必须和free()配套使用

在这里插入图片描述

示例 很容易忘记free
#include <stdio.h>
#include <stdlib.h>

int main()
{
    double *ptd;
    int max;//数组元素个数
    int number, i;

    printf("Enter the size of the double array:\n");
    if(scanf("%d", &max)!=1)
    {
        puts("Number not correctly entered.");
        exit(EXIT_FAILURE);
    }
    ptd = (double *)malloc(max * sizeof(double));
    if(!ptd)
    {
        puts("Memory allocation failed.");
        exit(EXIT_FAILURE);
    }
    //给数组输入值,输入值的数量是number,可以少于max
    printf("Enter up to %d values for the array.(q to end the input)\n", max);
    i = 0;
    while(i<max && scanf("%lf", &ptd[i])==1)
        i++;//从i<max退出则输入了max个值;从后面条件退出则输入少于max个
    printf("%d values have been entered.\n", number = i);//表达式number=i的值是number的值

    //输出数组的值
    for(i=0;i<number;i++)
    {
        printf("%7.2f ", ptd[i]);
        //一行7个
        if(i%7==6)
            putchar('\n');
    }
    //最后一行
    if(i%7!=0)
        putchar('\n');
    puts("Done!");

    free(ptd);

    return 0;
}
  • 我发现我对while和scanf控制输入掌握的不是很美丽

  • 输出时,每7个一行,使用求模运算符,不错的,值得学习

  • 我忘记了free()

Enter the size of the double array:
23
Enter up to 23 values for the array.(q to end the input)
1.23 1.24 4.56 3.4 6.7 2.5 6.8 9.2 4.67 8.91 8.94
q
11 values have been entered.
   1.23    1.24    4.56    3.40    6.70    2.50    6.80
   9.20    4.67    8.91    8.94
Done!

在这里插入图片描述在这里插入图片描述
注意,free的参数和malloc返回的指针可以不是同一个,只要指向同样的地址就ok。但是绝对不能释放一块内存两次!!!

不free()的后果:内存泄漏

memory leakage在这里插入图片描述
在这里插入图片描述

每次调用gobble函数,都会分配16000字节的动态内存,如果不free,可能循环还没结束,内存就耗尽了 ;就算循环顺利结束了,由于之前每次循环都没有把指针传回主调函数,所以指针没了,没法访问这1000块内存了,甚至想释放也释放不了,这些内存也没法被重复使用,就很尴尬····

内存泄漏的解决方法:不要忘记free······

calloc()

在这里插入图片描述在这里插入图片描述

感觉malloc和calloc没啥区别,只是malloc的参数只有一个,是需要的字节数;而calloc实现同样功能,把参数拆解为2个,一个是数组元素个数,一个是类型占用的字节数。

总结

理想化设想:程序把内存分为三部分,(不是物理上分为三部分,而是逻辑上),一部分供静态变量(无链接,内部链接,外部链接)使用,一部分供自动变量使用,一部分供动态内存分配使用。

静态内存在程序载入时就固定了,一直不变。

自动内存通常被作为栈处理,即新创建的变量按照顺序加入内存,却以相反的顺序被销毁。

动态内存分配完全由程序员管理,动态分配的内存可以在一个块中创建,另一个块中销毁。动态内存一般叫做内存堆或者自由内存。

动态内存比栈内存慢。

示例 程序把内存分为三部分

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int static_store = 30;//静态变量
const char *pcg = "String Literal";//静态变量
int main()
{
    int auto_store = 40;//自由变量
    char auto_string[] = "Auto char Array";//自由变量
    int *pi;
    char *pcl;

    pi = (int *)malloc(sizeof(int));
    *pi = 35;
    pcl = (char *)malloc(strlen("Dynamic String") + 1);
    strcpy(pcl, "Dynamic String");

    printf("static_store: %d at %p\n", static_store, &static_store);//静态变量
    printf("  auto_store: %d at %p\n", auto_store, &auto_store);//自动变量
    printf("         *pi: %d at %p\n", *pi, pi);//动态内存
    printf(" %s at %p\n", pcg, pcg);//静态变量
    printf("%s at %p\n", auto_string, auto_string);//自由变量
    printf("%s at %p\n", pcl, pcl);//动态内存
    printf("%s at %p\n", "Quoted String", "Quoted String");//字面量,静态内存

    free(pi);
    free(pcl);
    return 0;
}

可以看到,静态变量的static_store,pcg, "Quoted String"都在一起
动态内存的 ∗ * pi,pcl在一起
自由变量auto_store,auto_string在一起

static_store: 30 at 0040a004
  auto_store: 40 at 0061ff24
         *pi: 35 at 00db18f8
 String Literal at 0040b044
Auto char Array at 0061ff14
Dynamic String at 00db1908
Quoted String at 0040b0b0

类型限定符(幂等的)

C描述一个变量,通常使用类型和存储类别两个方面。

但是还可以从恒常性,易变性,等方面描述和限定。

用const 和volatile声明的类型是限定类型(qualified type)。

const 恒常性(constantcy)

C90增加的变量属性

const声明的对象的值不能通过赋值,递增递减等方式修改,使得变量只读。

在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

volatile 易变性(volatility)用于提高编译器优化

C90增加的变量属性
在这里插入图片描述
在这里插入图片描述

restrict 用于提高编译器优化

C99增加的变量属性
在这里插入图片描述
在这里插入图片描述

_Atomic

C11增加的变量属性

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值