全局变量

  • 定义在函数外面的变量是全局变量
  • 全局变量具有全局的生存期和作用域,它们与任何函数都无关,在任何函数内部都可以使用它们

例子:

#include<stdio.h>

int f(void);
int gAll = 12;

int main(int argc,int *argv[])
{
    printf("in %s gAll=%d\n",__func__,gAll);
    f();
    printf("in %s gAll=%d\n",__func__,gAll);
    return 0;
}

int f(void)
{
    printf("in %s gAll=%d\n",__func__,gAll);
    gAll = gAll + 2;
    printf("in %s gAll=%d\n",__func__,gAll);
    return gAll;
}

其中__func__为当前函数的名字。

该代码的输出

说明全局变量和任何函数都没有关系。

全局变量初始化

  • 没有做初始化的全局变量会得到0值
  • 没有做初始化的指针会得到NULL值
  • 只能用编译时刻已知的值来初始化全局变量
  • 它们的初始化发生在main函数之前

被隐藏的全局变量

  • 如果函数内部存在与全局变量同名的变量,则全局变量被隐藏

静态本地变量

  • 在本地变量定义时加上static修饰符就成为静态本地变量
  • 当函数离开的时候,静态本地变量会继续存在并保持其值(生存期全局,作用域本地)
  • 静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值
  • 静态本地变量实际上是特殊的全局变量,它们位于相同的内存区域

例子:

#include<stdio.h>

int f(void);

int main(int argc,int *argv[])
{
    f();
    f();
    f();
    return 0;
}

int f(void)
{
    static int all = 1;
    printf("in %s all=%d\n",__func__,all);
    all = all + 2;
    printf("in %s all=%d\n",__func__,all);
    return all;
}

 

运行结果如下:

 

返回指针的函数

  • 返回本地变量的地址是危险的
  • 返回全局变量或静态本地变量的地址是安全的
  • 返回在函数内malloc的内存是安全的,但是容易造成问题
  • 最好的做法是返回传入的指针

Tips

  • 不要使用全局变量来在函数间传递参数和结果
  • 尽量避免使用全局变量
  • 使用全局变量和静态本地变量的函数是线程不安全的

可变数组实现

怎么样实现一个大小可变的数组?(只考虑输入正常的情况,暂不考虑越界等问题)

  • 数组可以增长
  • 能够方便的得到数组的大小
  • 能够访问数组中的单元

需要实现的函数:

  • Array array_create(int init_size);//创建一个初始大小为init_size的int类型数组
  • void array_free(Array *a);//释放数组
  • int array_size(const Array *a);//获得数组此时的大小
  • int array_get(const Array *a , int index);//访问数组的第index个单元
  • void array_set(Array *a , int index , int value);//设置数组的第index个单元
  • void array_inflate(Array *a,int more_size);//给数组增加more_size个单元

实现代码如下:

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

 typedef struct{
      int *array;
      int size;
 }Array;

Array array_create(int init_size);//创建一个初始大小为init_size的int类型数组
void array_free(Array *a);//释放数组
int array_size(const Array *a);//获得数组此时的大小
int array_get(const Array *a , int index);//访问数组的第index个单元
void array_set(Array *a , int index , int value);//设置数组的第index个单元
void array_inflate(Array *a,int more_size);//给数组增加more_size个单元

Array array_create(int init_size)//创建一个初始大小为init_size的int类型数组
{
    Array a;
    a.size = init_size;
    a.array = (int*)malloc(a.size*sizeof(int));
    return a;
}

void array_free(Array *a)//释放数组
{
     free(a->array);
     a->array = NULL;
     a->size = 0;
}

int array_size(const Array *a)//获得数组此时的大小
{
     return a->size;
}

int array_get(Array *a,int index)//访问数组的第index个单元
{
     return a->array[index];

}

void array_set(Array *a,int index ,int value)//设置数组的第index个单元
{
     if(index >= a->size){
        array_inflate(a,(index/20+1)*20 - a->size);
     }
      a->array[index] = value;
}

void array_inflate(Array *a,int more_size)//给数组增加more_size个单元
{
    int *p = (int*)malloc((a->size+more_size)*sizeof(int));
     for(int i = 0; i<a->size ;i++){
        p[i] = a->array[i];
     }
    free(a->array);
    a->size =a->size+more_size;
    a->array = p;
}

 int main(){
      Array a = array_create(1);
      array_set(&a,0,1);
      printf("array_get(&a,0)=%d\n",array_get(&a,0));
      printf("array_size(&a)=%d\n",array_size(&a));
      array_set(&a,1,10);
      printf("array_size(&a)=%d\n",array_size(&a));
      printf("array_get(&a,0)=%d\n",array_get(&a,0));
      printf("array_get(&a,1)=%d\n",array_get(&a,1));
 }

 

题目描述:

n条恶龙闯入了王国的领土,为了拯救王国的危机,国王任命你前往冒险者工会雇佣勇者前去讨伐恶龙。每条恶龙的战斗力为ai。每个勇者的战斗力为bi,雇佣他的花费为ci。只有当勇者的战斗力大于等于恶龙的战斗力时,勇者才能击败恶龙。因为勇者都是骄傲的,所以勇者们拒绝互相组队(即每个勇者只能独自一人去讨伐恶龙)。勇者们都具有凝聚空气中魔力的能力,因此可以无限次出战。王国需要金币去灾后重建,需要你计算打败所有恶龙的最小花费。

输入:

第一行输入一个整数T,表示数据的组数。1≤T≤10。 第一行输入n,m,表示龙的数量和冒险者的数量。0<n,m≤10000。 接下来n行,每行一个数字ai表示龙的战斗力。 接下来m行,每行分别为bi和ci,表示冒险者的战斗力和雇佣他的花费。0<=ai,bi,ci≤100000。

输出:

如果能击退恶龙们,请输出最小的花费,如果不能,请输出”Kingdom fall”

样例输入:

2

1 1

6

10 3

3 2

10

14

15

9 10

6 7

样例输出:

3 Kingdom fall

 

先对每个骑士和龙的能力进行sort排序,然后遍历骑士和龙,用最小的代价将龙一条一条击退就好啦~
AC代码:

#include<iostream>
#include<limits.h>
#include<algorithm>
using namespace std;

struct maoxianjia
{
    double att;
    double mesos;
};

int comp(const maoxianjia &a,const maoxianjia &b)
{
    if(a.att == b.att)
    {
        return a.mesos<b.mesos;
    }
    else
        return a.att>b.att;
}

int main()
{
    int t = 0;
    int n = 0;
    int m = 0;
    int sum = 0;
    int mini;
    cin>>t;
    while(t--)
    {
        sum = 0;
        cin>>n>>m;
        int a[n+1];
        for(int i = 0; i < n; i++)
        {
            cin>>a[i];
        }
        maoxianjia b[m+1];
        for(int i = 0; i < m ; i++)
        {
            cin>>b[i].att>>b[i].mesos;
        }
        sort(a,a+n);
        sort(b,b+m,comp);
        if(b[0].att<a[n-1])
        {
            cout<<"Kingdom fall"<<endl;
            continue;
        }
        else
        {
            mini = INT_MAX;
            for(int i = 0; i <n ; i++)
            {
                for(int j = 0; j<m ; j++)
                {
                    if(a[i] > b[j].att)
                    {
                        break;
                    }
                    if(mini > b[j].mesos)
                    {
                        mini = b[j].mesos;
                    }
                }
                sum = sum + mini;
                mini = INT_MAX;
            }
        }
        cout<<sum<<endl;
    }
}

ACLLib是一个纯教学用途的纯C语言图形库,它并非任何产业界在使用的图形库,也不会有机会发展成为流行的图形库。它只是我们为了C语言学习的目的用的非常简单的图形库。它基于MS Windows的Win32API,所以在所有的Windows版本上都能使用。但是也因此它无法做成跨平台的库在其他操作系统上使用。

ACLLib在github上开源,网址是:https://github.com/wengkai/ACLLib

ACLLib是一个基于Win32API的函数库,提供了相对较为简单的方式来做Windows程序。

Windows API

  • 从第一个32位的Windows开始就出现了,就叫做Win32API
  • 它是一个纯C的函数库,就和C标准库一样,使你可以写Windows应用程序
  • 过去很多Windows程序是使用这个API做出来的

main()

  • main()成为C语言的入口函数其实和C语言本身无关,你的代码是被一小段叫做启动代码的程序所调用的,它需要一个叫做main的地方
  • 操作系统把你的可执行程序装载到内存里,启动运行,然后调用你的main函数
  • Win32API启动代码寻找的入口函数就是WinMain()而不是main()

CodeBlocks使用ACLLib图形库配置方法:

  1. 去https://github.com/wengkai/ACLLib下载src目录下的两个主要文件acllib.c和acllib.h
  2. 打开CodeBlocks,File->New->Project,创建一个新的项目,选择空项目即可
  3. 在项目的目录下新建一个main.c文件,并将acllib.c和acllib.h复制到该项目目录下,通过CodeBlocks的Project->Add file将这两个文件都添加到项目里来
  4. 通过CodeBlocks的Setting->Compiler,选择Linker settings,在CodeBlocks安装目录下的MinGW文件夹中的lib文件夹中找到下列8个文件add进去,点击OK即可。
  5. 然后打开main.c,即可开始写我们的代码啦~

我们ACLLib的程序的基本框架如下:(main.c)

#include "acllib.h"

int Setup(){
    initWindow("test",DEFAULT,DEFAULT,width,width);
    return 0;
}

我们的入口函数是大写开头的Setup(),在里面可以使用initWindow()去初始化一个窗口。

void initWindow(const char title[] , int left , int top , int width , int height);

参数含义依次为:窗口的标题,窗口的左边起始坐标,上面起始坐标,窗口宽度,窗口高度。

在窗口里面画图的代码要放在beninPaint()和endPaint()之间,代表画图的开始和画图的结束,任何绘图函数的调用必须在这一对函数调用之间才会生效。

在Windows中,坐标是以像素点的数字来定义的。对于你创建出来的窗口,左上角是(0,0),x轴自左向右增长,而y轴自上向下增长。

在终端窗口中,如果需要使用scanf()和printf()进行输入输出,则首先要使用initConsole()初始化,并且要记得加上stdio.h这个头文件,然后就可以在那个终端窗口使用scanf()和printf()了。

  • void putPixel(int x,int y,ACL_Color color);//参数列表依次为x坐标,y坐标,颜色
  • ACL_Color getPixel(int x, int y);

颜色

  • RGB(r,g,b)
  • 红色->RGB(255,0,0)
  • 图形库预先定义了一些颜色可以直接使用,如BLACK,RED,GREEN,BLUE,CYAN,YELLOW,WHITE,MAGENTA

线

  • void moveTo(int x, int y);//移动当前坐标到(x,y)
  • void moveRel(int dx , int dy);//将当前位置移动dx和dy的相对距离
  • void line(int x0,int y0,int x1,int y1);//画一条从(x0,y0)到(x1,y1)的直线
  • void lineTo(int x,int y);//从当前点画线到(x,y)
  • void lineRel(int dx,int dy);//从当前位置点到与当前点相对距离为dx和dy的点画一直线
  • void arc(int nLeftRect,int nTopRect,int nRightRect,int nBottomRect,int nXStartArc,int nYStartArc,int nXEndArc,int nYEndArc);//画一条圆弧,参数列表依次为完整圆弧外接矩形的左坐标,顶部坐标,右坐标,底部坐标,圆弧开始的x坐标,y坐标,结束的x坐标,y坐标

画笔

  • void setPenColor(ACL_Color color);//给画笔设置颜色
  • void setPenWidth(int width);//设置画笔的宽度,单位为像素点
  • void setPenStyle(ACL_Pen_Style style);//设置画笔的风格
  • PEN_STYLE_SOLID//实线———-
  • PEN_STYLE_DASH//虚线 – – – – –
  • PEN_STYLE_DOT//点线 ··········
  • PEN_STYLE_DASHDOT//虚线加点线-·-·-·-·-·-·
  • PEN_STYLE_DASHDOTDOT//虚线加两个点的线-··-··-··-··
  • PEN_STYLE_NULL//画一条看不见的线

  • void chrod(int nLeftRect , int nTopRect , int nRightRect , int nBottomRect, int nXRadia1l,int nYRadial1,int nXRadial2,int nYRadial2);//画扇形
  • void ellipse(int nLeftRect , int nTopRect,int nRightRect,int nBottomRect);//画椭圆
  • void pie(int nLeftRect , int nTopRect , int nRightRect , int nBottomRect, int nXRadia1l,int nYRadial1,int nXRadial2,int nYRadial2);//画饼
  • void rectangle(int nLeftRect , int nTopRect,int nRightRect, int nBottomRect);//画矩形
  • void roundrect(int nLeftRect,int nTopRect,int nRightRect,int nBottomRect,int nWidth,int nHeight);//画圆角矩形

刷子

  • 画笔负责线及面的边缘,刷子负责面的内部
  • void setBrushColor(ACL_Color color);//设置刷子的颜色
  • void setBrushStyle(ACL_Brush_Style style);//设置刷子的风格
  • BRUSH_STYLE_SOLID
  • BRUSH_STYLE_HORIZONTAL
  • BRUSH_STYLE_VECRTICAL
  • BRUSH_STYLE_FDIAGONAL
  • BRUSH_STYLE_BDIAGONAL
  • BRUSH_STYLE_CROSS
  • BRUSH_STYLE_DIAGCROSS

文字

  • void setTextColor(ACL_Color color);//设置文字的颜色
  • void setTextBkColor(ACL_Color color);//设置文字的背景颜色
  • void setTextSize(int size);//设置文字的字体大小
  • void setTextFont(char *pFontName);//设置文字的字体风格
  • void paintText(int x,y,const char *pStr);//以坐标点(x,y)为左上角写文字,文字内容为字符串pStr

练手画了一个Microsoft的Logo。

#include "acllib.h"

int Setup(){
    initConsole();

    int width = 400;
    initWindow("Microsoft",DEFAULT,DEFAULT,width,width);
    beginPaint();

    setPenColor(RGB(246,83,20));
    setBrushColor(RGB(246,83,20));
    rectangle(0,0,200,200);//左上角

    setPenColor(RGB(124,187,0));
    setBrushColor(RGB(124,187,0));
    rectangle(200,0,400,200);//右上角

    setPenColor(RGB(0,161,241));
    setBrushColor(RGB(0,161,241));
    rectangle(0,200,200,400);//左下角

    setPenColor(RGB(255,187,0));
    setBrushColor(RGB(255,187,0));
    rectangle(200,200,400,400);//右下角

    endPaint();
    return 0;
}

 

union

union A{
    int i;
   char c;
}a1;

如上的union会选择成员是

  • 一个int i 或
  • 一个char c

sizeof(union …) = sizeof(每个成员)的最大值

存储

  • 所有的成员共享一个空间
  • 同一时间只有一个成员是有效的
  • union的大小是其最大的成员
  • 初始化时会对第一个成员做初始化

union的用处

#include <stdio.h>

typedef union
{
    int i;
    char ch[sizeof(int)];
} CHI;

int main()
{
    CHI chi;
    chi.i = 1234;
    for(int i = 0; i <sizeof(int); i++)
    {
        printf("%02hhX",chi.ch[i]);
    }
    printf("\n");
    return 0;
}

 

运行结果为D2040000

这是为什么呢?

1234转化为16进制结果为00 00 04 D2,由于我使用的机器为基于X86架构的CPU,所以我的机器是一种小端的机器,低位在前高位在后,所以00 00 04 D2实际被存储为D2 04 00 00,所以我们的结果输出为D2040000。

我们可以通过这个方法得到一个整数内部的各个字节,同样也可以得到double等类型内部的各个字节,当我们要做文件操作时,当我们要把一个整数以二进制的形式写到一个文件里去的时候,都可以用这个方法来做读写的中间媒介。

什么是内存对齐?
关于什么是内存对齐,我们先来看几个例子。

struct str1 {
int a;
char b;
double c;
};

sizeof(str1)=16。

struct str2 {
int a;
double b;
char c;
};

sizeof(str2)=24。

struct str3 {
double a;
int b;
char c;
char d;
};

sizeof(str3)=16。

sizeof(str1)=16而sizeof(str2)=24,sizeof(str3)=16。为什么会产生不一样的结果呢?
这是非常简单的一个例子,体现了结构体的内存对齐规则。

 

  1. 结构体变量的起始地址能够被其最宽的成员大小整除
  2. 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
  3. 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
  4. 编译器在编译的时候是可以指定对齐大小的,上述说的只是默认情况,在Windows下这个默认值为8,在Linux下这个默认值为4,使用如下语句可以改变这个默认值。#pragma pack(n),其中n为对其大小。

快速计算方法可以总结为两个公式:

公式1:前面的地址必须是后面的地址正数倍,不是就补齐
公式2:整个Struct的地址必须是最大字节的整数倍

注意:

C语言和C++中空类和空结构体的大小
在C++中规定了空结构体和空类的内存所占大小为1字节,因为c++中规定,任何不同的对象不能拥有相同的内存地址。
而在C语言中,空的结构体在内存中所占大小为0。(gcc中测试为0,其他编译器不一定)

为什么要内存对齐?
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

对于字符串,C语言提供了很多函数来帮助我们处理字符串,这些函数都存在于头文件string.h中。

常用的字符串处理函数有:

  • strlen
  • strcmp
  • strcpy
  • strcat
  • strchr
  • strstr

 

strlen

  • size_t strlen(const char *s);
  • 返回s的字符串长度

实现代码如下:

#include<stdio.h>

int myStrLen(const char *str)
{
    int cnt = 0;
    while(str[cnt] != '\0')
    {
        cnt++;
    }
    return cnt;
}
int main(int argc ,char const *argv[])
{
    char line[] = "Hello World";
    printf("strlen=%lu\n",myStrLen(line));
    return 0;
}

 

strcmp

  • int strcmp(const char *s1,const char *s2);
  • 比较两个字符串,返回:
  • 0:s1==s2
  • 大于0:s1>s2
  • 小于0:s1<s2

数组实现代码如下:

#include<stdio.h>
int myStrCmp(const char *str1,const char *str2)
{
    int idx = 0;
    while(str1[idx]== str2[idx] && str1[idx]!='\0'){
        idx++;
    }
    return str1[idx]-str2[idx];
}
int main(int argc ,char const *argv[])
{
    char s1[] = "Hello World";
    char s2[] = "Hello Worle";
    printf("%d\n",myStrCmp(s1,s2));
    return 0;
}

 

指针实现代码如下:

#include<stdio.h>
int myStrCmp(const char *str1,const char *str2)
{
    int idx = 0;
    while(*str1== *str2 && *str1!='\0')
    {
        str1++;
        str2++;
    }
    return *str1-*str2;
}
int main(int argc ,char const *argv[])
{
    char s1[] = "Hello World";
    char s2[] = "Hello Worle";
    printf("%d\n",myStrCmp(s1,s2));
    return 0;
}

 

strcpy

  • char * strcpy(char *restrict dst,const char *restrict src);
  • 把src的字符串拷贝到dst
  • restrict表明src和dst不重叠(C99)
  • 返回dst

复制一个字符串的常用方法

char *dst = (char*)malloc(strlen(src)+1);

strcpy(dst,src);

数组实现代码如下:

#include<stdio.h>

char* myStrCpy(char *dst,char *src)
{
    int idx = 0;
    while(src[idx] != '\0')
    {
        dst[idx] = src[idx];
        idx++;
    }
    dst[idx] = '\0';
    return dst;
}
int main(int argc ,char const *argv[])
{
    char s1[] = "";
    char s2[] = "Hello World";
    myStrCpy(s1,s2);
    printf("%s\n",s1);
    return 0;
}

 

指针实现代码如下:

#include<stdio.h>

char* myStrCpy(char *dst,char *src)
{

    while(*src != '\0')
    {
        *dst = *src;
        dst++;
        src++;
    }
    *dst = '\0';
    return dst;
}
int main(int argc ,char const *argv[])
{
    char s1[] = "";
    char s2[] = "Hello World";
    myStrCpy(s1,s2);
    printf("%s\n",s1);
    return 0;
}

 

strcat

  • char *strcat(char *dst,const char *src);
  • 把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest原来末尾的“\0”)。
  • 要保证*dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。
  • 返回指向dest的指针。

实现代码:

#include<stdio.h>

char* myStrCat(char *dst,const char *src)
{
    char *temp = dst;
    while(*dst!='\0')
    {
        dst++;
    }
    while(*src!='\0')
    {
        *dst = *src;
        dst++;
        src++;

    }
    *dst = '\0';
    return temp;
}
int main(int argc ,char const *argv[])
{
    char s1[] = "Hello ";
    char s2[] = "World";
    myStrCat(s1,s2);
    printf("%s\n",s1);
    return 0;
}

 

strchr

  • char* strchr(const char *s , char c);
  • 返回首次出现字符c的位置的指针,如果字符串s中不存在字符c则返回NULL。

实现代码:

#include<stdio.h>
char* myStrChr(char *s,char c)
{
    while(*s!='\0' && *s!= c)
    {
        s++;
    }
    return *s==c ? s+1 : NULL;
}
int main(int argc ,char const *argv[])
{
    char s1[] = "Hello World";
    char s2 = 'W';
    char *ptr = myStrChr(s1,s2);
    printf("%s\n",ptr);
    return 0;
}

 

strstr

  • char* strstr(char *str,const char *str2);
  • str1:被查找目标
  • str2:要查找目标
  • 返回值:若str2是str1的子串,则返回str2在str1的首次出现的地址;如果str2不是str1的子串,则返回NULL。

实现代码:

#include<stdio.h>
char* myStrStr(char *s1,const char *s2)
{
    bool flag = false;
    char *temp1 = s1;
    char *temp2 = (char*)s2;
    while(*s1 != '\0' )
    {
        if(*s2 == '\0')
        {
            flag = true;
            break;
        }
        else if(*s1==*s2)
        {
            s1++;
            s2++;
        }
        else
        {
            s1++;
            s1 = ++temp1;
            s2 = temp2;
        }
    }
    if(flag)
    {
        return temp1;
    }
    else return NULL;
}
int main(int argc ,char const *argv[])
{
    char s1[] = "Hello World";
    char s2[] = "Wor";
    char *ptr = myStrStr(s1,s2);
    printf("%s\n",ptr);
    return 0;
}

main函数一般带有参数,如下

int main(int argc ,char const *argv[])

argv[0]是命令本身,当使用Unix的符号链接时,反映符号链接的名字。

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

int main(int argc ,char const *argv[])
{
    int i;
    for( i = 0; i<argc; i++)
    {
        printf("%d:%s\n",i,argv[i]);
    }
    return 0;
}

 

当使用命令行执行的时候,可以在后面加上参数,程序会读取到后面的参数并保存在argv数组中。

其中argv[0]为命令的名字(可执行程序的名字)。

如果输入数据时,先告诉你个数,然后再输入,要记录每个数据。

C99可以用变量做数组定义的大小,C99之前呢?

int *a = (int*)malloc(n*sizeof(int));
代码如下:

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

int main()
{
    int number = 0;
    int *a = 0;
    int i = 0;
    printf("输入数量:");
    scanf("%d",&number);
    a = (int*)malloc(number*sizeof(int));
    for( i = 0; i<number; i++)
    {
        scanf("%d",&a[i]);
    }
    for( i = number - 1; i>=0; i--)
    {
        printf("%d ",a[i]);
    }
    free(a);
}

 

malloc()

#include<stdlib.h>

void * malloc(size_t size);

1.向malloc申请的空间的大小是以字节为单位的

2.返回的结果是void*,需要类型转换为自己需要的类型

3.如果申请失败则返回0,或者NULL

 

试一试你的系统能给你多大的空间:

 

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

int main()
{
    void *p;
    int cnt = 0;
    while(p = malloc(100*1024*1024))
    {
        cnt++;
    }
    printf("分配了%d00MB的空间\n",cnt);
    return 0;
}

 

在我的电脑(64位 WIndows10家庭中文版,4G内存)上只能分配1900M的空间。

下面是来自知乎的讲解:

地址空间限制是有的,但是malloc通常情况下申请到的空间达不到地址空间上限。内存碎片会影响到你“一次”申请到的最大内存空间。比如你有10M空间,申请两次2M,一次1M,一次5M没有问题。但如果你申请两次2M,一次4M,一次1M,释放4M,那么剩下的空间虽然够5M,但是由于已经不是连续的内存区域,malloc也会失败。系统也会限制你的程序使用malloc申请到的最大内存。Windows下32位程序如果单纯看地址空间能有4G左右的内存可用,不过实际上系统会把其中2G的地址留给内核使用,所以你的程序最大能用2G的内存。除去其他开销,你能用malloc申请到的内存只有1.9G左右。

其实,操作系统版本、程序本身大小、乃至的动态/共享库数量和大小、程序栈数量和大小等都会对其造成影响,甚至有些操作系统使用了一种叫做随机地址空间分布的技术(主要是出于安全考虑,防止程序受恶意攻击),使得进程的堆空间变小。

一个由C/C++编译程序占用内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数参数值,局部变量值等。其操作方式类似于数据结构中栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量存储是放在一块,初始化全局变量和静态变量在一块区域, 未初始化全局变量和未初始化静态变量在相邻另一块区域。 – 程序结束后由系统释放。–>分别是data区,bbs区
4、文字常量区 —常量字符串就是放在这里。 程序结束后由系统释放–>coment区
5、程序代码—存放函数体二进制代码。–>code区

而我们的malloc()申请的空间位于堆区,32位操作系统下理论最大可能申请到的空间是4G(内存达到4G且不算操作系统占用的内存时,实际做不到),64位操作系统下基本是根据内存决定的。

 

free()

1.把申请得来的空间还给“系统”

2.申请过的空间最终都应该还

3.只能还申请来的空间的首地址

4.free(0)和free(NULL)没问题,程序什么都不会归还

 

那么,malloc得到的空间是连续的吗?

逻辑地址连续,物理地址可以不连续。

malloc在大多实现中分配得到的内存空间比要求的大,额外空间记录管理信息——分配块的长度。

 

关于malloc(0)

1.来自C99的最权威的解释,malloc(0)是未定义行为:

2.返回一个地址空间是有意义的:为了和free的正常配对

3.malloc(0)的结果依赖实现

 

putchar

  1. int putchar(int c)
  2. 向标准输入写一个字符
  3. 返回写了几个字符,EOF(-1)表示写失败

getchar

  1. int getchar(void)
  2. 从标准输入读入一个字符
  3. 返回类型是int是为了返回EOF(-1)
  4. Windows下 Ctrl+Z表示读入EOF状态
  5. Unix下 Ctrl+D表示读入EOF状态
#include<stdio.h>
#include<stdlib.h>

int main(int argc ,char const *argv[])
{
    int ch;
    while((ch = getchar())!=EOF)
    {
        putchar(ch);
    }
    return 0;
}