C calloc

calloc:堆中分配m个连续 初始化为0的字节块 内存,返回首字节地址

 
void *calloc(size_t 多少个字节块, size_t 每个字节块多少个字节)

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

int main()
{
    float *f ;
    f = (float*)calloc(3,4);
    printf("f:%f\n", f[0]);  //f:0.000000
    f[0] = 1;
    f[1] = 2;
    f[2] = 3;
    printf("f:%f\n", f[2]);  //f:3.000000
    
    float *f2 ;
    f2 = (float*)malloc(3*4);
    f2[1] = 4;
    printf("f2:%f\n", f2[1]);//f2:4.000000

    return 0;
}


分配(通常是临时开辟一段连续的内存),释放 举例

 
void flatten(float *x, int size, int layers, int batch, int forward)
{
    float *swap = calloc(size*layers*batch, sizeof(float));
    int i,c,b;
    for(b = 0; b < batch; ++b){
        for(c = 0; c < layers; ++c){
            for(i = 0; i < size; ++i){
                int i1 = b*layers*size + c*size + i;
                int i2 = b*layers*size + i*layers + c;
                if (forward) swap[i2] = x[i1];
                else swap[i1] = x[i2];
            }
        }
    }
    memcpy(x, swap, size*layers*batch*sizeof(float));
    free(swap);
}

c float

float有效小数位:小数点后6位

 
#include <stdio.h> 

int main(){
    float a = 0.1234567;
    printf("%f\n",a);  // 0.123457
    printf("size of float :%d\n",sizeof(a)); //size of float :4
    return 0;
}

如果小数点后超出6位,直接四舍五入
        

c float的精度

 
#include <stdio.h>
int main(){
    float a = 1234567890.1234567;
    printf("a = %f\n", a);  // a = 1234567936.000000
    return 0;
}

float小数点后6位有效小数,但它的有效位数是7,第8位就开始不准了

float的存储结构

 
float类型数据占四个字节,一个字节是8位,共32位
以类似 3.402823466 E+38 的格式存储

第1位:  0表示正数,1表示负数
第2-9位:指数
第10-32:几点几的小数部分

AI中的小数

 
import numpy as np
np.allclose(1e-8,0)  # True 

AI中认为小数点高于7位的部分为0,
而C中float高于6位就自动四舍五入
第8位就是0,第7位四舍五入到第6位上,即合理又不浪费
还减少了计算的代价,

AI框架最终还是要转化为C的float类型进行计算,
而double的有效位数为15,对AI来说太多了,
所以AI中数据类型通常指定为float32 

float的最大值 :3.402823466 E+38

 
3.4乘以10的38次方是一个非常大的数
10的9次方为亿,其38次方不知道多少个亿
大部分运算使用float类型够用 

C fork

fork:创建一个从fork调用位置开始与父进程并行的子进程

 
fork调用位置之前的资源与父进程一致,从fork位置开始创建一个子进程

fork调用,创建一个子进程,此时已经有两个进程了:父进程,子进程 
fork返回时,父进程返回子进程的pid,子进程的fork返回的是0 
这两个返回跟对方已经没任何关系了
这时候的代码有两份,各自执行各的,互不影响,因为他们在两个进程内

有点像小说中的分身的意思,你炼制分身之前,这世界上只有一个你,
你克隆一个你自己,从此之后你们是两个独立的个体了

 


    

 
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid;
    int i;

    // 调用 fork 创建子进程
    pid = fork();

    if (pid < 0) {
        // fork 失败
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
        // 子进程
        for (i = 0; i < 100; i++) {
            printf("子进程: %d\n", i);
        }
    } else {
        // 父进程
        printf("父进程: %d\n", pid);
    }

    printf("------main over-----\n");

    return 0;
}
    

注意 main over输出了两次;另外,主进程执行完毕后,代码执行没有结束,直到子进程也执行完才结束

 
xt@qisan:/opt/tpf/cwks/alg02$ ./a.out 
父进程: 9029
------main over-----
子进程: 0
子进程: 1
子进程: 2
子进程: 3
子进程: 4
子进程: 5
...
...
...
子进程: 96
子进程: 97
子进程: 98
子进程: 99
------main over-----

 


 


 

  

 


c free

free释放malloc/calloc在堆中分配的内存

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

int main(){
    char a[3]={1,2,3};
    // free(a);  // warning: ‘free’ called on unallocated object ‘a’ [-Wfree-nonheap-object]
    
    char *ss = "abc";
    // free(ss);  // c1.c:8:5: warning: ‘free’ called on a pointer to an unallocated object ‘"abc"’ [-Wfree-nonheap-object]
    
    char *p ;
    p = (char *)malloc(sizeof(char));
    if (p != NULL){
        free(p);
    }

    return 0;
}

free函数原型: void free(void *ptr);
free只用于释放malloc/calloc分配的内存,除此之外就给你报错 

对于一个普通变量,char a,这个a在栈中分配空间,定长,a的值是一个char型字符
对于一个指针变量,char *p,这个p在栈中分配空间,定长,p的值是32/64位地址
该地址指向了堆中某个位置,free就是要去释放从这个位置开始到指定长度的空间的内存

free参数是指针,释放的是指针对应的内存空间,但栈上的地址不需要free,会自动释放,

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

int main()
{

    char* ss = "wa ka ka";
    // free(ss); //warning: incompatible implicit declaration of built-in function ‘free’ [enabled by default]

    printf("%d\n",sizeof(char));  //1

    char* s1;
    s1 = (char *)malloc(9*sizeof(char));
    printf("1 :%p\n",s1);  //1 :0xbab010 为s1分配了地址

    //这行是错误的,s1已经有指向的地址,接下来是往其指向的地址赋值,而不是改变这个指向
    //C语言中的变量,都是在分配地址时就指定了长度,最多扩展地址对应的长度,没见过改地址的
    // s1 = "wa ka ka"; 

    strcpy( s1, "wa ka ka");

    printf("2 :%p\n",s1);//2 :0xbab010

    //free释放的是malloc分配的内存空间,表示这些空间可以由其他程序使用了,
    //它并不改变s1指向的地址,只是s1指向的地址它不能用了
    if(s1 != NULL){
        free(s1);
    }
    printf("3 :%p\n",s1);//3 :0xbab010

    s1 = NULL;
    printf("4 :%p\n",s1);//4 :(nil) 这下连指向的地址也没了 

    return 0;
}

C fgets

char *fgets(char *str, int n, FILE *stream);

 
从指定的流 stream 读取一行,并把它存储在str所指向的字符串内。
当读取(n-1)个字符时,或者读取到换行符时,或者到达文件末尾时,停止

之所以是读取n-1个,而不是n个,是因为字符串默认会多一个\0字符,
比如,输入abc,实际上占4个字符,读取3个字符,就是所输入的字符 

这个流,可以是标准输入,也可以是文件 

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

int main() {
    char str[100];
    printf("请输入一个字符串:");
    fgets(str, sizeof(str), stdin); // 从标准输入读取字符串
    printf("您输入的内容:%s\n", str);
    return 0;
}
        

$ ./a.out 
请输入一个字符串:abc
您输入的内容:abc

顺带提一下stdin,stdin就是“用户区”(与内核区STDIN_FILENO对应)的标准输入,代表从键盘输入

fgets 从文件中读取

 
#include <stdio.h> 
int main()
{
    FILE *fp = NULL;
    fp = fopen("/tmp/a.log", "w+");   // w+ 表示可读可写
    fprintf(fp, "%d: ",1);            // 格式化输入,要输入什么类型先指定格式
    fprintf(fp, "每天 学习 五分钟\n");  // 不指定默认为字符串
    fputs("2: 每天 运动 半小时\n", fp); // 专写字符串
    fclose(fp);

    char buff[255];
    
    fp = fopen("/tmp/a.log", "r");
    fscanf(fp, "%s", buff);      //遇到空格或换行符时,停止读取
    printf("%s\n", buff );       //1:
    
    fgets(buff, 255, (FILE*)fp); //读一行或255个字符
    printf("%s\n", buff );       //  每天 学习 五分钟
    
    fgets(buff, 255, (FILE*)fp); 
    printf("3: %s\n", buff );    //3: 2: 每天 运动 半小时
    fclose(fp);
}

$ gcc c.c 
$ ./a.out 
1:
 每天 学习 五分钟

3: 2: 每天 运动 半小时

c inline

 
inline关键字加在方法的 “定义”前面,
可将方法的内容直接嵌入该方法被调用的地址,
可省去方法调用的性能消耗

通常用在调用频率极高的地方:这样才会考虑去节省每次方法调用消耗的那一点点损耗

 
#include <stdio.h>

static inline float distance_from_edge(int x, int max)
{
    int dx = (max/2) - x;
    if (dx < 0) dx = -dx;
    dx = (max/2) + 1 - dx;
    dx *= 2;
    float dist = (float)dx/max;
    if (dist > 1) dist = 1;
    return dist;
}
    
int main ()
{
    int i;
    for(i =0;i<5;i++){
        float d = distance_from_edge(i, 2);
        printf("%f\n",d);
    }
    return 0;
}

什么叫调用频率极高:比如千万级以上的运算,矩阵运算等

C malloc

malloc:堆中分配n个连续 字节 的内存,返回首字节地址

 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
    
int main()
{
    char *pp;

    /* 动态分配内存 */
    pp = (char *)malloc(100 * sizeof(char));
    if( pp == NULL )
    {
        fprintf(stderr, "error: - unable to allocate required memory\n");
    }else
    {
        strcpy( pp, "静心,三思而后行");
    }
    printf("pp: %s\n", pp );
    free(pp);
}       

malloc与数组区别

 
linux OS进程内存有栈,堆两类,
编辑时用到的内存放在栈里,比如数组;
malloc在堆里分配内存;

数组生命周期结束时,其占用内存自动释放;
malloc分配的内存可以使用free函数由程序员提前释放;

C memcpy

字符指针到字符数组

 
#include <stdio.h>
#include <string.h>
    
int main ()
{
    char *path1 = "/tmp";
    int count = strlen(path1);

    char ss[100];
    memcpy(ss,path1,count);
    printf("%s--\n",ss);  //tmp--

    memcpy(ss,path1,count+1);
    printf("%s--\n",ss);  ///tmp--

    return 0;
}

count与count+1结果一样,
这是因为字符串最后一个字符'\0'代表空/终止,C编辑器会自动处理,所以才一样
推荐+1
memcpy比strcpy多一个参数,这个参数表示要copy的字节数

要地址 还是要内容

 
#include <stdio.h>
#include <string.h> 
    
int main ()
{
    // char* c = "bbb";
    char c[] = "bbb";
    char* a1 = "/tmp";
    char a2[10];

    char* b = c;

    a1 = b;  //a1指针变量,值为一个地址,地址变了 

    //memcpy,将内容copy走,不涉及地址变更
    //从此,两份内容之间再也没有关系了 
    memcpy(a2, b, strlen(b)+1);

    printf("a1=%s--\n",a1);  //a1=bbb--

    c[2]='c';

    //不允许使用memcpy将字符串中赋值,但字符数组是可以的 
    //这也是字符数组与字符串的一个区别
    // memcpy(a1, b, strlen(b)+1); //Segmentation fault (core dumped)

    printf("a1=%s--\n",a1);  ///a1=bbc--
    printf("a2=%s--\n",a2);  ///a2=bbb--

    return 0;
}

    

 
指针变量 终还是一个变量,其值是个地址 

这不是内容,内容改变,地址未必改变 

memcpy 专门COPY内容
    
C memset

按字节填充数据块

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

int main(){
    int a[3];
    int i;
    for(i=0;i<3;i++){
        printf("%d ",a[i]);
    }
    memset(a,0,sizeof(a));
    printf("\n");
    for(i=0;i<3;i++){
        printf("%d ",a[i]);
    }
    printf("\n");

    return 0;
}

 
还可以对char* ss字符指针进行填充,此时0还代表着NULL,即字符串的结束 
也不是处处需要填充,字符串类操作会自动在字符结束处填充上0 

C NULL

NULL与0等价

 
#include <stdio.h>
int main(){
    printf("%d\n",NULL);   //0
    printf("%d\n",0==NULL);//1
    return 0;
}

NULL作为参数

 
#include <stdio.h> 

void test(char* ss){
    if (ss!=NULL){
        printf("%s\n",ss); 
    }else{
        printf("params is NULL\n");
    }
}

int main(){
    char* s1 = "123";
    test(NULL);
    test(s1);
}

$ gcc b.c
$ ./a.out 
params is NULL
123

C pipe

linux pipe

 
管道是两个进程之间的连接,一个进程的标准输出成为另一个进程的标准输入。
在UNIX操作系统中,管道用于进程间通信。

pipe特性

 
类似文件,能缓冲数据,可将之看作内存中的“虚拟文件”,相比文件,它又多了一些约束

单向通信,一个进程向管道写入数据,另一个进程从管道读取数据

进程间共享信息,管道可以被 创建进程及其所有子进程 读写。一个进程可以写入这个“虚拟文件”或管道,另一个相关进程可以从中读取

原子性,如果在某个内容完全写入管道之前,某个进程试图读取该内容,则该进程将挂起,直到内容被写入

管道系统调用 在进程的 打开文件表中 找到 前两个可用位置,并将其分配给管道的读取和写入端。

c pointer

指针可以当作一维数组

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

int main()
{
    float *f ;
    float arr[3] = {1,2,3};
    f = arr ;
    printf("f:%f\n", f[0]);  //f:1.000000
    printf("f:%f\n", f[3]);  //f:0.000000,这一行表示指针可越界

    float a1 = 11;
    f = &a1;
    printf("f:%f\n", f[0]);  //f:11.000000,这一行表示单个数也成数组

    return 0;
}

数组定长,在初始化数组时,需要明确给出数组元素的个数
而指针不需要确定要指多少个元素,
剩下的它们都一样 
        

字符数组与字符指针

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

int main(){
    typedef char string[];
    string s1 = "abc";
    char *s2 = "看上去是字符串,实际就是字符串";
    s2 = "ABC";

    // abc--3--4
    printf("%s--%ld--%ld\n",s1,strlen(s1),sizeof(s1));
    printf("%s\n",s2);
    return 0;
}

strlen只针对 const char * 类型,不是这个类型的,无法使用strlen计算长度 
从这点也可以看出C中的字符串,是定长的多个字符形成的常量

数字数组与数字指针

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

int main(){
    typedef int ints[];
    ints s1 = {1,2,3};
    int *s2 ;
    s2 = s1;

    // 1--12
    printf("%d--%ld\n",s1[0],sizeof(s1));
    printf("%d\n",s2[0]);
    return 0;
}

数组与指针的区别:是否定长

 
对于指针,下面是正确的:
char *s2 = "看上去是字符串,实际就是字符串";
s2 = "ABC";

对于数组,下面是错误的:
typedef char string[];
string s1 = "abc";
string s1 = "day day up";
报错如下:
a.c: In function ‘main’:
a.c:7:12: error: redefinition of ‘s1’
    7 |     string s1 = "day day up";
      |            ^~
a.c:6:12: note: previous definition of ‘s1’ with type ‘char[4]’
    6 |     string s1 = "abc";

虽然在定义string类型时,没有指定长度,
但在初始化string类型的变量s1时,s1的长度被固定为4,
并且后续不可对其修改
如果再换一个变量名,就没有问题,下面的是对的:
typedef char string[];
string s1 = "abc";
string s2 = "day day up";


typedef char string[];
这句虽然一定程度上实现了变长数组,但是有要求,
就是在定义变量时,必须对其初始化,以确定数组的长度;
下面的两句是错误的,因为没有对数组进行初始化,关键是没有指定数组的长度
typedef char string[];
string s1;

指针:变长的数组

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

#define N 3

typedef char string[];
struct Students
{
    int   student_id;  // 4 bytes 
    char* student_name; //sizeof在计算指针类型时,统一是16字节,计算的是地址的长度
    float score;        // 4 bytes
};

int main(){

    
    struct Students st1;
    st1.student_id = 1001;
    st1.student_name="南宫婉"; //一个汉字3字节,再加上1个自补的结束符,这三个汉字等价于char[10]
    // strcpy(st1.student_name,"南宫婉");//不支持这种写法,编辑报错 
    st1.score = 73;

    int size = sizeof(st1);
    printf("student1 size:%d\n",size);  // student1 size:24
    printf("student1.student_name :%s\n",st1.student_name);  // 

    return 0;
}

名称这一步如果使用下面的写法则编辑报错:error: flexible array member not at end of struct
string student_name;
无法做到在初始化变量时,让数组有固定的长度
而指针没有这个问题

指针变量可以换地址,而数组不可以

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

int main(){
    int s1[] = {1,2,3};
    int s2[] = {4,2,3,4};
    int* s3 = s1;
    s3 = s2;

    // 4--12
    printf("%d--%ld\n",s3[0],sizeof(s1));
    return 0;
}

{1,2,3},"abc"有一个共性,它们都是常量,指针指的一个常量的地址,
虽然s1,s2是变量,没有加const,但这些变量的值最终还是常量
指针指的是地址,对于一个地址来说,它里面放的就是常量,虽然它可以换成别的常量
对于一个地址本身,
这个地址如果是数组,它自带长度,如果是字符数组,还会有个结束标志'\0'

下面的代码会报错
#include <stdio.h>

int main(){
    int s1[] = {1,2,3};
    int s2[3] ;
    s2 = s1; // error: assignment to expression with array type
}

与指针相比,数组更像是加上了const的指针,不能再变地址了
初始化时,必须就确定其长度

指针变量的初始化就是确定一个地址,然后就看这个地址有什么作用了
从名字看,指针变量,指针变量,地址当然能变了,不然还叫什么指针变量 

**可以看作二维数组

 
float **vals;  
等价于二维数组 

指针变量的变 相对 数据地址的定

 
#include <stdio.h>
int main(){
    int s1[] = {1,2,3};
    int s2[3] ;
    s2 = s1; // error: assignment to expression with array type
}

 
s2 = s1 报错,说明数组的地址在初始化后,不可以再被改变了
int *p 指针变量的地址可以改变,是因为它本身就是被设计为 存储地址的变量

数组中可变的是地址中的元素,地址是不可变的 

c void*

void*:泛化/抽象类型

 

一个指针就是一个地址,有时不知道这个地址放的是何种类型的数据,
此时,可认为该地址存储数据的类型为 void* 

#include <stdio.h>
int main(){

    float a = 0.1234567;
    printf("a = %f\n", a);  // a = 0.123457

    float *b = &a;
    void *x = b;

    float b2 = *(float*)x;
    printf("b2 = %f\n", b2);// b2 = 0.123457

    double d = 3.1415926;
    printf("d = %f\n", d);

    void *d1 = &d;
    double d2 = *(double*)d1;
    printf("d2 = %f\n", d2);// d2 = 3.141593

    double d3 = 12345678901234567.1415926;
    printf("d3 = %f\n", d3);  //d3 = 12345678901234568.000000

    void *d4 = &d3;
    double d5 = *(double*)d4;
    printf("d5 = %f\n", d5);// d5 = 12345678901234568.000000

    return 0;
}

指针是地址,地址是内存字节的位置编码,是整数,通常以16进制(0x)存储
指针变量存的就是类似0x这样的一串数字
指针变量也是有类型的,可以是int,float,double,struct等,存什么数据就是什么类型

在为函数传参方便,所有的类型都可以是void*,
即先不问是什么类型,把参数先传进行,具体用时再转换回去

从上面的的例子额外多说明一个事,
double的精度是16位,超过16位的部分就不准了
在不超过16位的情况下,小数点后的精度最多为6位,其中第7位四舍五入到第6位上

void*在转换的过程中,完全不影响其精度的换算,还是其自身的精度

c float的精度

 
#include <stdio.h>
int main(){
    float a = 1234567890.1234567;
    printf("a = %f\n", a);  // a = 1234567936.000000
    return 0;
}

同double一样,小数点后6位有效小数,但它的有效位数是7,第8位就开始不准了

参考
    浮点型的存储方式
    
    c语言中void *的使用

    了解C语言中的pipe()系统调用 

    C++ free()用法及代码示例