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);
}
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类型够用
|
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----- |
|
|
|
|
|
|
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;
}
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: 每天 运动 半小时
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;
}
什么叫调用频率极高:比如千万级以上的运算,矩阵运算等
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函数由程序员提前释放;
字符指针到字符数组
#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内容
按字节填充数据块
#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
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
linux pipe
管道是两个进程之间的连接,一个进程的标准输出成为另一个进程的标准输入。 在UNIX操作系统中,管道用于进程间通信。
pipe特性
类似文件,能缓冲数据,可将之看作内存中的“虚拟文件”,相比文件,它又多了一些约束 单向通信,一个进程向管道写入数据,另一个进程从管道读取数据 进程间共享信息,管道可以被 创建进程及其所有子进程 读写。一个进程可以写入这个“虚拟文件”或管道,另一个相关进程可以从中读取 原子性,如果在某个内容完全写入管道之前,某个进程试图读取该内容,则该进程将挂起,直到内容被写入 管道系统调用 在进程的 打开文件表中 找到 前两个可用位置,并将其分配给管道的读取和写入端。
指针可以当作一维数组
#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 指针变量的地址可以改变,是因为它本身就是被设计为 存储地址的变量 数组中可变的是地址中的元素,地址是不可变的
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()用法及代码示例