【c1】数据类型,指针,结构体,main参数,static/extern,typedef,编译预处理,gdb,makefile,文件,多线程,动静态库

文章目录


1.数据类型:编译器(compiler)与解释器(interpreter),中文里的汉字和标点符号是两个字节,不能算一个字符(单引号)

编译型语言:C/C++:并且都是AOT(ahead of time)预先编译,编译将源码编译成机器码生成二进制文件,可直接运行该文件,因为是机器码,所以运行效率很高。缺点:不够灵活,改代码要重新编译,此外平台依赖,linux平台编译出来的二进制文件无法在windows运行,跨平台还需借助交叉编译。
解释型语言:python/js/php:不需要生成二进制文件,灵活如线上php系统,改了代码功能直接生效,但运行效率低。
半解释半编译型语言:java:JIT将运行到的代码块在运行时编译成机器码,既可保证跨平台性,又能使热代码高效运行。
C库函数声明头文件(.h):存放在/usr/include目录中,如果 #include <facebook/cJSON.h>,则cJSON.h在/usr/include/facebook/目录里。如下github上软件包,在image.bb中添加hiredis软件包在yocto或common或meta-aspeed或meta-phosphor文件夹中查找hiredis.bb文件【内容有git网址】,.h文件会在/usr/include/中,但rootfs中看不到。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
C库函数定义(具体实现)文件(.c):gcc编译不用包含libgcc.a(缺省会包含,/usr/lib/gcc/x86_64…/4.4.4/libgcc.a即打包好的静态库)。

gcc main.c -o main  -Wall(显示所有警告)  -I../include(-I后面没空格,不加-I就在main.c中指定头文件的相对路径)   -L../lib   -lsum(有库文件/lib/libsum.so就只要main.c就行)

1.1 float是如何在计算机中存储:d(十进制),o(八进制),x(十六进制)

2个字节的-480在计算机中为0xfe20,ipmitool发指令即低位在前即0x20 0xfe。先确定字节数,再将最高位1固定。
在这里插入图片描述
如下没有用补码表示,-2+1应该=-1,但是下面变成了-3,所以负数存储用补码,方便计算机加减法。
在这里插入图片描述
如下用补码表示。
在这里插入图片描述
int是4字节即32bit,int的最大最小值如下。
在这里插入图片描述
如下10.625存储:符号位只占一位,指数存的是移动的位数+127。
在这里插入图片描述

num=5 # 0000 0101
result=$((~num))  # 得到: 1111 1010
echo $result  # -6  # 上面负数在计算机中存储是取反加1,1000 0101 + 1 = 1000 0110(-6)
# 所以~作用是不管符号位,起到加1作用: 0000 0101 + 1 = 6

num=-5 # 负数存储是补码即取反加1 :1111 1011
result=$((~num))  # 得到: 0000 0100
echo $result  # 4

在这里插入图片描述
在这里插入图片描述
sizeof运算符(不是函数)计算某一个变量在当前系统的内存中所需占用的字节数:sizeof结果类型是size_t,typedef unsigned int/long size_t:
1.用于数据类型:sizeof(int)) = 4,sizeof(指针)大小永远是8字节。
2.用于变量:sizeof(var_name)。
在这里插入图片描述
while(真)执行,continue跳到循环首部,break跳出循环。switch case语句里定义了新的变量 , case里加{}。
在这里插入图片描述
在这里插入图片描述
如下do-while先执行一次循环。
在这里插入图片描述

1.2 数组:存数据类型相同的数据,数组下标越界导致段错误

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如下如果用msg[1],虽然越界了,但是系统给结构体分配很多字节,所以不报错。可用msg[0]到msg[n-1]。
在这里插入图片描述

int main()
{
    int value = 1;
    char str[10] = "-q1"; // NA:0 , -1:-1 , 1:1 , q1:0
    value = atoi(str);    //字符串转换为整数(不是ascii码), strtol
    printf("%d\n",value); //0
}

#define PLATFORM_NAME_PATH      "./a.py"
void get_machine_ver(char * result)
{
    char buffer[32] = {0};
    char cmd[128] = {0};
    strcpy(cmd, "python3 ");
    strcat(cmd, PLATFORM_NAME_PATH);
    FILE* file = popen(cmd, "r");
    if (file == NULL) {
        return;
    }

    // memset(buffer, '\0', sizeof(buffer));
    fgets(buffer, sizeof(buffer), file);
    printf("111,%ld\n",sizeof(buffer));  // 32   // char buffer[32] = {0};
    printf("222,%ld\n",strlen(buffer));  // 14 连换行符

    strncat(result, buffer, strlen(buffer)-1); // 去除换行符  // memcpy(result, buffer, strlen(buffer)-1); result长度不知,请确保 result 缓冲区足够大,以容纳 buffer 中的数据,并且不会发生缓冲区溢出的情况。
    // while (fgets(buffer, 10, file) != NULL) {
    //     strcat(result, buffer);
    // }
    pclose(file);
    return;
}

int main(){
    char result[32] = {0};
    get_machine_ver(result);
    printf("333,%s",result); // 333, HP1-2C4F-0..
}

char name[21];
memset(name,0,sizeof(name));
strcpy(name, "豫让");
printf("我的姓名是%s。\n",name);

int main()
{
	  char *names = "PSU";
	  if(strcmp(names,"PSU")==0)
	  {
		  puts("aaaa");  // 打印出
	  }
}

int main() {   // error: expected ‘)’ before string constant    少了int main()
    char name_str[30];
    char name_str1[30];
    strcpy(name_str,"/sys/bus/i2c/devices/");
    strcpy(name_str1,"17-0064");
    strcat(name_str,name_str1);
    printf("is : %s\n", name_str);   // /sys/bus/i2c/devices/17-0064  
     // printf("[%s]__%4d__[%s]  %s \n", __FILE__, __LINE__, __FUNCTION__, n->name);
}

int main ()
{
   char str1[15];
   char str2[15];
   int ret;
   strcpy(str1, "abcdef");
   strcpy(str2, "ABCDEF");
   ret = strcmp(str1, str2);
   if(ret < 0)
   {
      printf("str1 小于 str2");
   }
   else if(ret > 0) 
   {
      printf("str1 大于 str2");
   }
   else 
   {
      printf("str1 等于 str2");
   }
   return(0);    //str1 大于 str2
}
 
void cp (char *path_from, char *path_to)
{
	FILE *fp_read = NULL;
    FILE *fp_write = NULL;
    char ch = !EOF;
    fp_read = fopen(path_from, "r");
    // if (fp_read == NULL)
    // {
    //     printf("您没有这个(%s)文件\n", path_from);
    // }
    fp_write = fopen(path_to, "w");
    while ((ch = fgetc(fp_read)) != EOF) // 读
    {
        fputc(ch,fp_write);   // 写
    }
    fclose(fp_read);
    fclose(fp_write);
}

int main(void)
{
    char path_from[50];
    char path_to[50];
    printf("输入文件名:"); // D:\1.txt
    scanf("%s", path_from);
	printf("\n输入文件名:"); // D:\2.txt,不存在会自动创建
    scanf("%s", path_to);
    cp(path_from,path_to);
    return 0;
}
#include<stdio.h>

char str[10];
char strbuf[10];
char *a(void)
{
    FILE *fp=0;
    if ((fp=fopen("a","rt")) ==0)  //a文件里写了 333aaa
    {
        printf("111");
    }
    if (fgets(strbuf, 10, fp) == NULL) 
    {
        printf("222");
        fclose(fp);
    }
    strcpy(str,"B");
    strcat(str,strbuf);
    fclose(fp);
    return str;
}

int main()
{
    char *b=a();
    printf("%s\n", b);  // B333aaa
}

int main()
{
    FILE *fp=0;
    if ((fp = fopen("/tmp/bbb","r")) == 0)
    {
        printf("file do not exist\n");
        fclose(fp); // Segmentation fault (core dumped),fp不存在不能close,上行没打印出是因为fclose报错早于printf
        return -1;
    }
    fclose(fp); // 走不到
    return 0;
}

int main()
{
    int len,i=0;
    char * pid_name_config_1[] = {"GPU111111110","CPU0_DIMM0","MOC2.5_CPU"};
    printf("000,%s\n",pid_name_config_1[0]);  // 000,GPU111111110
    printf("111,%ld\n",sizeof(pid_name_config_1[0])); // 111,8(指针大小)

    printf("222,%ld\n",sizeof(pid_name_config_1)); // 222,24(3*8)
    len = (sizeof(pid_name_config_1)/sizeof(pid_name_config_1[0]));
    printf("333,%d\n",len); // 333,3

    printf("444,%ld\n",sizeof("GPU0")); // 444,5(最后\0结束符)
    printf("555,%ld\n",strlen("GPU0")); // 555,4

    char pid_name[20]={0};  // char pid_name[20][20]={0}; 可以,每一行用来存字符串,不是字符
    for(i=0; i<len; i++){
        strcpy(pid_name[i], pid_name_config_1[i]); // 错误,strcpy参数expected ‘char * restrict’ but argument is of type ‘char
        memcpy(pid_name[i], pid_name_config_1[i], strlen(pid_name_config_1[i])); // 同上
        printf("666,%s\n", pid_name[i]);
    }
}
char temp_log_0[100] = {0};
sprintf(temp_log_0, " '%d' ", a);
char temp_log_1[100] = "echo ";
strcat(temp_log_1, temp_log_0);
char temp_log_2[100] = " >> /var/log/a.log";
strcat(temp_log_1, temp_log_2);
system(temp_log_1);  // C
if(-1 == std::system(temp_log_1)){}; 
if(-1 == std::system("echo 'aaa' >> /var/log/a.log")){};

2.指针:指针数组:这个数组的所有元素都是指针类型。数组指针:这个指针存放着一个数组的首地址

2.1 地址/值/变量名:p是地址,*p是指向的内容,01指0x01,&a:拿变量a的地址赋给

在这里插入图片描述
int *p 未赋值的指针称为野指针(危险),a=100,p=&a指向合法区域。或如下空指针的两种写法也可防止野指针。
在这里插入图片描述

2.2 函数调用:复制a/指向a

在这里插入图片描述
1.如下main中走到increament跳到这函数中,拷贝一份给increament中的a。main中的a和increament中的a各自独立一块内存,只是名字一样。
在这里插入图片描述
2.一定要通过increasement函数对a有修改怎么办?如下必须用到指针,increament运行结束后指针销毁。
在这里插入图片描述
在这里插入图片描述

2.3 函数返回:复制指针

如下当调用move_p时,p要往右移一位:move_p参数定义虽是指针,但调用时传入也是指针,复制一份。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 数组和指针转换:指针转不了数组,数组可转为指针,&取地址

在这里插入图片描述

3.结构体:存放一组不同数据类型的数据

在这里插入图片描述
如下最后一行*pst就是queen结构体变量。
在这里插入图片描述
结构体复制:基本类型用=,字符串用strcpy,结构体memcpy。
在这里插入图片描述
结构体作为函数的参数:结构体成员较多,函数参数的初始化和赋值的开销很大,最好的办法就是传递结构体变量的地址。
在这里插入图片描述

4.main函数的参数:main函数的参数是从命令提示符下执行程序时传入

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

#include <stdio.h>
int main(int argc, char **argv) {
  char *stty, *dev;
  dev = argv[1];
  stty = argv[2];
  printf("%s\n%s\n",dev,stty);
}

在这里插入图片描述

// a.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

extern int h_errno;
int main(int argc, char **argv)
{
	char   *ptr, **pptr;
	char    str[INET_ADDRSTRLEN];
	struct hostent *hptr;  //
	while (--argc> 0) 
	{
		ptr = *++argv;  //传入的域名
		if ( (hptr = gethostbyname (ptr) ) == NULL)   //完成域名解析
		{
			printf("gethostbyname error for host: %s: %s",ptr, hstrerror (h_errno) );
			continue;
		}
		printf ("official hostname: %s\n", hptr->h_name);

		for (pptr=hptr->h_aliases; *pptr!= NULL; pptr++)
			printf ("\talias: %s\n", *pptr);

		switch (hptr->h_addrtype) 
		{
			case AF_INET:
				pptr = hptr->h_addr_list;
				for ( ; *pptr != NULL; pptr++)
					printf ("\taddress: %s\n",inet_ntop (hptr->h_addrtype, *pptr, str, sizeof (str)));  //hptr->h_addrtype我们获取的IP地址
				break;
			
			default:
				printf("unknown address type");
				break;
		}
	}
	exit(0);
}

$ gcc a.c -o a
$ ./a www.baidu.com
official hostname: www.a.shifen.com
        alias: www.baidu.com
        address: 180.101.50.188  (浏览器输入都会跳转到百度)
        address: 180.101.50.242

5.static/inline/extern:inline内联函数本身就是static(本文件私有)函数,inline函数在c中作用性不高,c中用习惯了宏定义

如head.h中定义inline f1函数并实现,其他文件只要include "head.h"就能调用f1,宏在编译预处理时用文本形式复制过来,inline函数先编译成二进制再插入调用的地方,不用跳转到f1执行。

什么函数被定义成内联函数呢?1.经常被调用(如果不经常被调用,节省一点时间也没意义),2.函数体里代码少(如果函数体里代码多,执行时间远大于跳转时间)。

如下方框是一个c文件,右边的c文件可以调到左边的var变量,每一个c文件不管里面写什么都可以编成o文件,右边的o文件var地址留空。最后链接成二进制文件时,链接就是编译,所以和1(外链)和2(内链)相关。
在这里插入图片描述
extern表示引用外部的变量,从外面来的。
在这里插入图片描述

6.typedef:define仅仅是简单的字符串的替换,而typedef则给这个类型新起了一个名字

6.1 给已定义的变量类型起个别名:给struct __person起了个别名person_t

struct __person
{
    char    name[20];
    uint8_t age;
}
typedef __person person_t;
//以上两段代码也可合并为一段:
typedef struct __person
{
	...
}person_t;

6.2 定义函数指针类型:必须用typedef,方法就是在“定义函数指针变量”前加上typedef

int (*pFunc)(char *frame, int len);  //定义了一个函数指针变量pFunc,它可以指向这样的函数:返回值为int,形参为char*、int
int *(*pFunc[5])(int len);  //定义了5个函数指针变量:pFunc[0]、pFunc[1]···,它们都可以指向这样的函数:返回值为int*,形参为int

//举例:
typedef  int (*pFunc_t)(char *frame, int len); //定义了一个类型pFunc_t 
int read_voltage(char *data, int len)
{
    int voltage = 0;
    return voltage;
}
int main(void)
{
    pFunc_t  pHandler = read_voltage;  //使用类型pFunc_t来定义函数指针变量
}

6.3 定义数组指针类型:先看如何定义数组指针变量

int(*pArr)[5]; //定义了一个数组指针变量pArr,pArr可以指向一个int[5]的一维数组
char(*pArr)[4][5]; //定义了一个数组指针变量pArr,pArr可以指向一个char[4][5]的二维数组

//举例:
int(*pArr)[5]; //pArr是一个指向含5个int元素(因为最前面是int)的一维数组的指针变量
int a[5] = {1,2,3,4,5};
int b[6] = {1,2,3,4,5,6};

pArr = &a; //完全合法,无警告
pArr = a; //发生编译警告,赋值时类型不匹配: a的类型为int(*) 相当于首地址,而pArr的类型为int(*)[5]
pArr = &a[0]; //发生编译警告,赋值时类型不匹配: a的类型为int(*),而pArr的类型为int(*)[5]

pArr = &b; //发生编译警告,赋值时类型不匹配:&b的类型为int(*)[6],而pArr的类型为int(*)[5]
pArr = (int(*)[5])&b; //类型强制转换为int(*)[5],完全合法,无警告,但复杂,简化如下:

typedef int(*pArr_t)[5];//定义一个指针类型,该类型的指针可以指向含5个int元素的一维数组
int main(void)
{
    int a[5] = {1,2,3,4,5};
    int b[6] = {1,2,3,4,5,6};
    pArr_t pA;//定义数组指针变量pA
    pA= &a;//完全合法,无警告    
    pA= (pArr_t)&b;//类型强制转换为pArr_t,完全合法,无警告
}

6.4 定义数组类型:声明一个含5个int元素的一维数组:int a[5]

// 声明多个含5个int元素的一维数组:int a1[5], a2[5], a3[5]···,或者 a[N][5]   复杂,这时应该把数组定义为一个类型:
typedef int arr_t[5];
int main(void)
{
    arr_t d;  //d是个数组,这一行等价于:  int d[5];
    arr_t b1, b2, b3; //b1, b2, b3都是数组
    d[0] = 1;
    d[5] = 253; //编译警告:下标越界
}

#define MaxNumbersOfName 10
#define MaxNumbersOfPhones 20
typedef char Elections_type[MaxNumbersOfNames]; // 10
Elections_type Phone[MAXNambersOfPhones]={"HUAWEI","XIAOMI","SAMSUNG","APPLE"}// char Phone[10][20]相同
typedef struct _jmp_buf
{ 
    int _jb[_JBLEN + 1]; 
} jmp_buf[1]; 
// jmp_buf(定义变量实体的同时,也获得了该变量的地址)是一个含一个元素的数组类型,数组的元素为struct  _jmp_buf类型

jmp_buf buf;      //这一行等价于:struct _jmp_buf buf[1]
buf->_jb[5] = 34; //这一行等价于:(&buf[0])->_jb[5] = 34
handle(buf);      //这一行等价于:handle(&buf[0])

#define char* pchar;
typedef pchar_type char*;
// 只有p1,p2,p3被成功定义字符指针变量
pchar_type p1,p2;
pchar p3,p4;  // 在编译的时候就会被编译器看作char *p3,p4;造成了p4是一个char类型的变量

7.编译预处理:C源程序 - 编译预处理【#开头指令和特殊符号进行处理,删除程序中注释和多余空白行】- 编译

在这里插入图片描述
条件编译:最常用的两种格式#ifdef和#ifndef 。#undef :取消已定义的标识符
在这里插入图片描述
在这里插入图片描述
如下book145.c和_public.c都有 #include"_public.h",会重复包含。
在这里插入图片描述
在_public.c中如下这样写,_public.h就不会被重复包含。
在这里插入图片描述

8.gdb调试:多进/线程中无法用

root 用户:yum -y install gdb,gdb -v。
在这里插入图片描述
在这里插入图片描述

9.makefile文件:make是一个解释makefile中指令的命令工具

vi gcc.sh 如下,sh gcc.sh。gcc -o 目标 依赖1 依赖2。makefile命令能被执行条件有两个:1.目标不存在,2.依赖已更新。
在这里插入图片描述
如上若只需要编译book2,单个文件改变不重复编译其他文件即增量编译。vi makefile,$前一个tab键不能8个空格。make默认是make all,如果将all这行book3删除,则make不会编译book3,可指定make book3,book3相当于标签。-欧2是让编译效率最高,一般正式发布用。gcc命令选项 :-c编译不链接。
在这里插入图片描述

10.文件:fprint/fgets/fwrite/fread,ftell/rewind/fseek/fflush

10.1 文本文件:FILE结构体

在这里插入图片描述
在这里插入图片描述
vi /tmp/test1.txt,可见有5行记录,不管执行多少次都是5行记录,因为文件打开方式是w,每次打开文件时都会清空原文件中的记录。
在这里插入图片描述
在这里插入图片描述

int main() {
    int fd;
    char buffer[5] = {0};
    ssize_t bytesRead;

    fd = open("/home_a/abcd", O_RDONLY, 0444);
    bytesRead = read(fd, buffer, 4);
    if (bytesRead == -1) {
        printf("read error");
        return 1;
    }
    printf("Read %zd bytes: %s\n", bytesRead, buffer);  //Read 4 bytes: 0xff
    return 0;
}

10.2 二进制文件:没有行概念

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

#include <stdio.h>
int main()
{
    char *filename = "Shanghai";
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
    {
        printf("打开%s文件错误", filename);
        return -1;
    }
    int n;
    for (int i = 0; i < 10000; i++)
    {
        fread(&n, sizeof(int), 1, fp);
        printf("%d\n ", n);
    }
    fclose(fp);
    return 0;
}

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

10.3 文件定位:linux下文本文件模式和二进制文件模式没有区别。fgets和fprintf以行方式读写文本文件,但不能读写二进制文件。用fread和fwrite可以读写文本文件和二进制文件

文件内部有一个位置指针,用来指向当前读写的位置,也就是读到第几个字节。在文件打开时,如果打开模式是r和w,位置指针指向文件的第一个字节。如果打开模式是a,位置指针指向文件的尾部,每当从文件里读n个字节或文件里写入n个字节后,位置指针会后移n个字节。

文件位置指针与C中指针不是一回事,位置指针仅仅是一个标志,表示文件读写到的位置即读写到第几个字节,不表示地址。文件每读写一次,位置指针就会移动一次,不需要你在程序中定义和赋值,由系统自动设置。
在这里插入图片描述

#include <stdio.h>
int main(int argc, char **argv) 
{
    FILE *fp=fopen("/sys/bus/i2c/devices/20-0048/hwmon/hwmon1/in0_input","w");
    if(!fp)
    {
        puts("fail");
    }
    fclose(fp);
}

FILE *fptime;
fptime=fopen("/tmp/time","w");
time_t time_log = time(NULL);
struct tm* tm_log = localtime(&time_log);
fprintf(fptime, "flag[%d]  LINE[%d]    %04d-%02d-%02d %02d:%02d:%02d\r\n",sensor_flag, __LINE__, tm_log->tm_year + 1900, tm_log->tm_mon + 1, tm_log->tm_mday, tm_log->tm_hour, tm_log->tm_min, tm_log->tm_sec);
fflush(fptime);
fclose(fptime);

if(rc<0)
{
   FILE *fpLedLog=fopen("/tmp/error","a");
   fprintf(fpLedLog,"error__%u__\r\n",__LINE__);
   fclose(fpLedLog);
   goto err;
}

FILE *fpLedColor=fopen(led_color,"w");
fseek(fpLedColor,0,SEEK_SET);
fprintf(fpLedColor,"%s",sensor_flag?LED_GREEN_CODE:LED_YELLOW_CODE);
fflush(fpLedColor);
fclose(fpLedColor);

int main(int argc, char **argv) 
{
     if(2 == argc)
     {
         FILE *fpLedCtrl=fopen("/sys/bus/i2c/devices/0-000d/sys_led_ctrl","w");
         FILE *fpLedColor=fopen("/sys/bus/i2c/devices/0-000d/sys_led_color","w");
         FILE *fpLedLog=fopen("/var/log/sensorMon.log","w");    
         fprintf(fpLedCtrl,"0x1");
         fprintf(fpLedColor,"%s",argv[1]);
         fprintf(fpLedLog,"%s\r\n",argv[1]);    
         fclose(fpLedCtrl);
         fclose(fpLedLog);
         fclose(fpLedColor);
     }
}

int mysprintf(char *outBuffer, char *format, ...)
{
    va_list aptr;
    int ret;
    va_start(aptr, format);
    ret = vsprintf(outBuffer, format, aptr);
    va_end(aptr);
    return(ret);
}

if( realvalue >= 0 )
{
	CompareValueThreshold(realvalue,&node[i]);
	strcat(node[i].path,node[i].node);
	if(0==strcmp("P1V8_VDDO(SWITCH)",node[i].name) || 0==strcmp("P1V2(SWITCH)",node[i].name))
       RecordEventLog(LOG_ERR,"\n [%d] throw a %s\n",i,sta?"SENSOR_ABNORMAL":"SENSOR_NORMAL");
}

FILE *fright=fopen("/tmp/right","w");
for(i=0;i<arraysize;i++)
{
   fprintf(fright,"[%d] %s (%s)\r\n",i,node[i].path,node[i].desc);
}
fclose(fright);

11.多线程:pthread_create(),查看线程top -H,ps -xH | grep

11.1 子线程未执行:join

如下线程thread进程process区别:process不能共享内存。
在这里插入图片描述
如下线程主函数void* 。pthread_create的第四个参数是myfunc的参数。
在这里插入图片描述
在这里插入图片描述

11.2 线程传参区分线程:“th1”

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

11.3 两子线程数字相加:分别加到自己线程变量中

在这里插入图片描述
在这里插入图片描述
如下解决上面代码重复太多问题,将0-2500和2500-5000当参数传进来。
在这里插入图片描述
在这里插入图片描述

11.4 两个线程同时加到一个全局变量s中:5000数字小不会影响

在这里插入图片描述

全局变量S++要加锁:数字大出现race condition

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

11.5 假共享:两线程分别加到自己result数组中,0和1两个线程,两个result数组(一个数字累加)

在这里插入图片描述
在这里插入图片描述
如下定义s为局部变量 = 结构体取出result,比上面要快。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
time ./example6始终比example5快,将50000000多加一个0,快的更多。为什么 ? 因为假共享false sharing,如下是一个框即单核cpu不会false sharing。
在这里插入图片描述
如下多核+运算结果距离近:example5里result变量在线程主函数外,cpu线程计算要从RAM中拉取。example6里的s为局部变量放在两个线程主函数里即cpu缓存里做计算,cpu两个核里两个缓存不会互相影响。所以example6不会falsing sharing,速度快。
在这里插入图片描述
如下解决假共享:cpu的cache短,RAM里很长,第一个线程结果保存在0位置,第二个线程结果保存在100位置,cache只更新自己长度的一小段如下4段(空间换时间)。
在这里插入图片描述

#if 0
int a=200;
int b=100;
pthread_mutex_t lock; //互斥锁的宏
void ThreadA(void)
{
    printf("线程A.....\n");
    pthread_mutex_lock(&lock);
    a-=50; //a=a-50
    sleep(5);
    b+=50; //b=b+50
    printf("a:%d,b:%d\n",a,b);
    pthread_mutex_unlock(&lock);
}
void ThreadB(void)
{
    printf("线程B.....\n");
    sleep(1);
    pthread_mutex_lock(&lock);//加锁
    printf("%d\n",a+b);
    pthread_mutex_unlock(&lock);//解锁
}
int main(void)
{
    pthread_t tida,tidb;
    pthread_mutex_init(&lock,NULL);//建立一个互斥锁
    pthread_create(&tida,NULL,(void *)ThreadA,NULL); //创建一个线程,1.句柄,2.线程属性,3.线程函数,4.函数的参数
    pthread_create(&tidb,NULL,(void *)ThreadB,NULL);
    pthread_join(tida,NULL);//等待一个线程结束
    pthread_join(tidb,NULL);
    pthread_mutex_destroy(&lock);
    return 1;
}
// -server:~/bak$ gcc test.c -lpthread
// -server:~/bak$ ./a.out 
// 线程A.....
// 线程B.....
// a:150,b:150
// 300
#endif

12.动静态库:.a,指定.so,LD_

公用函数库的public.cpp是源代码,对任何人可见,实际开发出于保密并不希望提供公用函数库源代码。C/C++提供了一个保证代码安全性方法,public.cpp编译成库(静态库与动态库)。

// public.h
#ifndef PUBLIC_H
#define PUBLIC_H 1
#include <stdio.h>
void func();   // 自定义函数的声明
#endif
// public.cpp
#include "public.h"
void func()   // 自定义函数的实现
{
  printf("我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。\n");
}
// book265.cpp
#include "public.h"  // 把public.h头文件包含进来
int main()
{
  func();
}
g++ -o book265 book265.cpp public.cpp
./book265
我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。

12.1 静态库:链接库的文件名是libpublic.a,链接库名是public,缺点使用的静态库发生更新改变,程序必须重新编译

gcc -c -o libpublic.a public.cpp

使用静态库的方法一,直接把调用者源代码和静态库文件名一起编译:

g++ -o book265 book265.cpp libpublic.a

使用静态库的方法二,用L参数指定静态库文件的目录,-l参数指定静态库名:如果要指定多个静态库文件的目录,用法是“-L/目录1 -L目录2 -L目录3”;如果要指定多个静态库,用法是“-l库名1 -l库名2 -l库名3”

g++ -o book265 book265.cpp -L/home/w/demo -lpublic
./book265
我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。

12.2 动态库:动态库发生改变,程序不需要重新编译,动态库升级方便

g++ -fPIC -shared -o libpublic.so public.cpp

使用动态库的方法与使用静态库的方法相同。如果在动态库文件和静态库文件同时存在,优先使用动态库编译:

g++ -o book265 book265.cpp -L/home/w/demo -lpublic

执行程序./book265时,出现以下提示:/book265: error while loading shared libraries: libpublic.so: cannot open shared object file: No such file or directory,因为采用了动态链接库的可执行程序在运行时需要指定动态库文件的目录,Linux系统中采用LD_LIBRARY_PATH环境变量指定动态库文件的目录。采用以下命令设置LD_LIBRARY_PATH环境变量。

export LD_LIBRARY_PATH=/home/w/demo:.

如果要指定多个动态库文件的目录,用法是“export LD_LIBRARY_PATH=目录1:目录2:目录3:.”,目录之间用半角的冒号分隔,最后的圆点指当前目录。接下来修改动态库中func函数的代码:

// printf("我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。\n");
printf("生活美好如鲜花,不懂享受是傻瓜;\n");

如下重新编译动态库,无需重新编译book265,直接执行程序。

g++ -fPIC -shared -o libpublic.so public.cpp
./book265
生活美好如鲜花,不懂享受是傻瓜;

12.3 libc(标准):gnu libc(glibc)(实现)

编译【预处理(语法检查),编译(.c->.s汇编文件),汇编(.s->.o二进制文件),链接(多个.o合并成1个执行文件)】的最后阶段将依赖引入过程叫链接:so文件通过mmap加载进内存,动态链接的a.out文件小且内存占用小,此外动态链接在so库更新后不需重新编译,一般首选。很多进程用到C语言libc.so里stdio.h里打印函数,如果通过静态链接,这样占用的内存多。
在这里插入图片描述
在这里插入图片描述
static指定静态链接。gcc是gnu的编译工具集合,gcc不光编译c语言且支持很多平台
在这里插入图片描述
如下系统没有glibc库。
在这里插入图片描述

12.4 编译时为什么要加上 –lm ?:man exp:Link with -lm

// 代码一
#include <stdio.h>
#include <math.h> //exp
int main(int argc, char const *argv[]){
   printf("The exponential value of %lf is %lf\n", 0, exp(0));
   printf("The exponential value of %lf is %lf\n", 0+1, exp(0+1)); //e的1次幂
   printf("The exponential value of %lf is %lf\n", 0+2, exp(0+2));
   return(0);
}

在这里插入图片描述

// 代码二
#include <stdio.h>
#include <math.h>
int main(int argc, char const *argv[]){
   double x = 0;
   printf("The exponential value of %lf is %lf\n", x, exp(x));
   printf("The exponential value of %lf is %lf\n", x+1, exp(x+1));
   printf("The exponential value of %lf is %lf\n", x+2, exp(x+2));
   return(0);
}

在这里插入图片描述
在这里插入图片描述
代码一调用exp传入的参数是常量为0 。代码二调用exp传入的参数是变量 x,代码一会不会在运行之前就计算好了呢?如下代码一没有看到调用exp的身影,当传入参数为常量时就已计算好了值,最后不需调用exp函数。代码二通过如下main.s汇编代码可见多次调用call函数。
在这里插入图片描述
math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,程序中用到的数学函数要到这个库文件里找。

gcc a.c -o a.outarm-linux-gcc a.c -o b.out,如果执行out文件出现No such file or directory,则将如下两个so文件互相ln -s建软链接。
在这里插入图片描述

13.编译预处理:C源程序 - 编译预处理【#开头指令和特殊符号进行处理,删除程序中注释和多余空白行】- 编译

在这里插入图片描述
条件编译:最常用的两种格式#ifdef和#ifndef 。#undef :取消已定义的标识符
在这里插入图片描述
在这里插入图片描述
如下book145.c和_public.c都有 #include"_public.h",会重复包含。
在这里插入图片描述
在_public.c中如下这样写,_public.h就不会被重复包含。
在这里插入图片描述

14.gdb调试:多进/线程中无法用

root 用户:yum -y install gdb,gdb -v。
在这里插入图片描述
在这里插入图片描述

15.makefile文件:make是一个解释makefile中指令的命令工具

vi gcc.sh 如下,sh gcc.sh。gcc -o 目标 依赖1 依赖2。makefile命令能被执行条件有两个:1.目标不存在,2.依赖已更新。
在这里插入图片描述
如上若只需要编译book2,单个文件改变不重复编译其他文件即增量编译。vi makefile,$前一个tab键不能8个空格。make默认是make all,如果将all这行book3删除,则make不会编译book3,可指定make book3,book3相当于标签。-欧2是让编译效率最高,一般正式发布用。gcc命令选项 :-c编译不链接。
在这里插入图片描述

16.文件:fprint/fgets/fwrite/fread,ftell/rewind/fseek/fflush

16.1 文本文件:FILE结构体

在这里插入图片描述
在这里插入图片描述
vi /tmp/test1.txt,可见有5行记录,不管执行多少次都是5行记录,因为文件打开方式是w,每次打开文件时都会清空原文件中的记录。
在这里插入图片描述
在这里插入图片描述

int main() {
    int fd;
    char buffer[5] = {0};
    ssize_t bytesRead;

    fd = open("/home_a/abcd", O_RDONLY, 0444);
    bytesRead = read(fd, buffer, 4);
    if (bytesRead == -1) {
        printf("read error");
        return 1;
    }
    printf("Read %zd bytes: %s\n", bytesRead, buffer);  //Read 4 bytes: 0xff
    return 0;
}

16.2 二进制文件:没有行概念

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

#include <stdio.h>
int main()
{
    char *filename = "Shanghai";
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
    {
        printf("打开%s文件错误", filename);
        return -1;
    }
    int n;
    for (int i = 0; i < 10000; i++)
    {
        fread(&n, sizeof(int), 1, fp);
        printf("%d\n ", n);
    }
    fclose(fp);
    return 0;
}

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

16.3 文件定位:linux下文本文件模式和二进制文件模式没有区别。fgets和fprintf以行方式读写文本文件,但不能读写二进制文件。用fread和fwrite可以读写文本文件和二进制文件

文件内部有一个位置指针,用来指向当前读写的位置,也就是读到第几个字节。在文件打开时,如果打开模式是r和w,位置指针指向文件的第一个字节。如果打开模式是a,位置指针指向文件的尾部,每当从文件里读n个字节或文件里写入n个字节后,位置指针会后移n个字节。

文件位置指针与C中指针不是一回事,位置指针仅仅是一个标志,表示文件读写到的位置即读写到第几个字节,不表示地址。文件每读写一次,位置指针就会移动一次,不需要你在程序中定义和赋值,由系统自动设置。
在这里插入图片描述

#include <stdio.h>
int main(int argc, char **argv) 
{
    FILE *fp=fopen("/sys/bus/i2c/devices/20-0048/hwmon/hwmon1/in0_input","w");
    if(!fp)
    {
        puts("fail");
    }
    fclose(fp);
}

FILE *fptime;
fptime=fopen("/tmp/time","w");
time_t time_log = time(NULL);
struct tm* tm_log = localtime(&time_log);
fprintf(fptime, "flag[%d]  LINE[%d]    %04d-%02d-%02d %02d:%02d:%02d\r\n",sensor_flag, __LINE__, tm_log->tm_year + 1900, tm_log->tm_mon + 1, tm_log->tm_mday, tm_log->tm_hour, tm_log->tm_min, tm_log->tm_sec);
fflush(fptime);
fclose(fptime);

if(rc<0)
{
   FILE *fpLedLog=fopen("/tmp/error","a");
   fprintf(fpLedLog,"error__%u__\r\n",__LINE__);
   fclose(fpLedLog);
   goto err;
}

FILE *fpLedColor=fopen(led_color,"w");
fseek(fpLedColor,0,SEEK_SET);
fprintf(fpLedColor,"%s",sensor_flag?LED_GREEN_CODE:LED_YELLOW_CODE);
fflush(fpLedColor);
fclose(fpLedColor);

int main(int argc, char **argv) 
{
     if(2 == argc)
     {
         FILE *fpLedCtrl=fopen("/sys/bus/i2c/devices/0-000d/sys_led_ctrl","w");
         FILE *fpLedColor=fopen("/sys/bus/i2c/devices/0-000d/sys_led_color","w");
         FILE *fpLedLog=fopen("/var/log/sensorMon.log","w");    
         fprintf(fpLedCtrl,"0x1");
         fprintf(fpLedColor,"%s",argv[1]);
         fprintf(fpLedLog,"%s\r\n",argv[1]);    
         fclose(fpLedCtrl);
         fclose(fpLedLog);
         fclose(fpLedColor);
     }
}

int mysprintf(char *outBuffer, char *format, ...)
{
    va_list aptr;
    int ret;
    va_start(aptr, format);
    ret = vsprintf(outBuffer, format, aptr);
    va_end(aptr);
    return(ret);
}

if( realvalue >= 0 )
{
	CompareValueThreshold(realvalue,&node[i]);
	strcat(node[i].path,node[i].node);
	if(0==strcmp("P1V8_VDDO(SWITCH)",node[i].name) || 0==strcmp("P1V2(SWITCH)",node[i].name))
       RecordEventLog(LOG_ERR,"\n [%d] throw a %s\n",i,sta?"SENSOR_ABNORMAL":"SENSOR_NORMAL");
}

FILE *fright=fopen("/tmp/right","w");
for(i=0;i<arraysize;i++)
{
   fprintf(fright,"[%d] %s (%s)\r\n",i,node[i].path,node[i].desc);
}
fclose(fright);

17.多线程:pthread_create(),查看线程top -H,ps -xH | grep

17.1 子线程未执行:join

如下线程thread进程process区别:process不能共享内存。
在这里插入图片描述
如下线程主函数void* 。pthread_create的第四个参数是myfunc的参数。
在这里插入图片描述
在这里插入图片描述

17.2 线程传参区分线程:“th1”

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

17.3 两子线程数字相加:分别加到自己线程变量中

在这里插入图片描述
在这里插入图片描述
如下解决上面代码重复太多问题,将0-2500和2500-5000当参数传进来。
在这里插入图片描述
在这里插入图片描述

17.4 两个线程同时加到一个全局变量s中:5000数字小不会影响

在这里插入图片描述

全局变量S++要加锁:数字大出现race condition

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

17.5 假共享:两线程分别加到自己result数组中,0和1两个线程,两个result数组(一个数字累加)

在这里插入图片描述
在这里插入图片描述
如下定义s为局部变量 = 结构体取出result,比上面要快。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
time ./example6始终比example5快,将50000000多加一个0,快的更多。为什么 ? 因为假共享false sharing,如下是一个框即单核cpu不会false sharing。
在这里插入图片描述
如下多核+运算结果距离近:example5里result变量在线程主函数外,cpu线程计算要从RAM中拉取。example6里的s为局部变量放在两个线程主函数里即cpu缓存里做计算,cpu两个核里两个缓存不会互相影响。所以example6不会falsing sharing,速度快。
在这里插入图片描述
如下解决假共享:cpu的cache短,RAM里很长,第一个线程结果保存在0位置,第二个线程结果保存在100位置,cache只更新自己长度的一小段如下4段(空间换时间)。
在这里插入图片描述

#if 0
int a=200;
int b=100;
pthread_mutex_t lock; //互斥锁的宏
void ThreadA(void)
{
    printf("线程A.....\n");
    pthread_mutex_lock(&lock);
    a-=50; //a=a-50
    sleep(5);
    b+=50; //b=b+50
    printf("a:%d,b:%d\n",a,b);
    pthread_mutex_unlock(&lock);
}
void ThreadB(void)
{
    printf("线程B.....\n");
    sleep(1);
    pthread_mutex_lock(&lock);//加锁
    printf("%d\n",a+b);
    pthread_mutex_unlock(&lock);//解锁
}
int main(void)
{
    pthread_t tida,tidb;
    pthread_mutex_init(&lock,NULL);//建立一个互斥锁
    pthread_create(&tida,NULL,(void *)ThreadA,NULL); //创建一个线程,1.句柄,2.线程属性,3.线程函数,4.函数的参数
    pthread_create(&tidb,NULL,(void *)ThreadB,NULL);
    pthread_join(tida,NULL);//等待一个线程结束
    pthread_join(tidb,NULL);
    pthread_mutex_destroy(&lock);
    return 1;
}
// -server:~/bak$ gcc test.c -lpthread
// -server:~/bak$ ./a.out 
// 线程A.....
// 线程B.....
// a:150,b:150
// 300
#endif

18.动静态库:.a,指定.so,LD_

公用函数库的public.cpp是源代码,对任何人可见,实际开发出于保密并不希望提供公用函数库源代码。C/C++提供了一个保证代码安全性方法,public.cpp编译成库(静态库与动态库)。

// public.h
#ifndef PUBLIC_H
#define PUBLIC_H 1
#include <stdio.h>
void func();   // 自定义函数的声明
#endif
// public.cpp
#include "public.h"
void func()   // 自定义函数的实现
{
  printf("我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。\n");
}
// book265.cpp
#include "public.h"  // 把public.h头文件包含进来
int main()
{
  func();
}
g++ -o book265 book265.cpp public.cpp
./book265
我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。

18.1 静态库:链接库的文件名是libpublic.a,链接库名是public,缺点使用的静态库发生更新改变,程序必须重新编译

gcc -c -o libpublic.a public.cpp

使用静态库的方法一,直接把调用者源代码和静态库文件名一起编译:

g++ -o book265 book265.cpp libpublic.a

使用静态库的方法二,用L参数指定静态库文件的目录,-l参数指定静态库名:如果要指定多个静态库文件的目录,用法是“-L/目录1 -L目录2 -L目录3”;如果要指定多个静态库,用法是“-l库名1 -l库名2 -l库名3”

g++ -o book265 book265.cpp -L/home/w/demo -lpublic
./book265
我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。

18.2 动态库:动态库发生改变,程序不需要重新编译,动态库升级方便

g++ -fPIC -shared -o libpublic.so public.cpp

使用动态库的方法与使用静态库的方法相同。如果在动态库文件和静态库文件同时存在,优先使用动态库编译:

g++ -o book265 book265.cpp -L/home/w/demo -lpublic

执行程序./book265时,出现以下提示:/book265: error while loading shared libraries: libpublic.so: cannot open shared object file: No such file or directory,因为采用了动态链接库的可执行程序在运行时需要指定动态库文件的目录,Linux系统中采用LD_LIBRARY_PATH环境变量指定动态库文件的目录。采用以下命令设置LD_LIBRARY_PATH环境变量。

export LD_LIBRARY_PATH=/home/w/demo:.

如果要指定多个动态库文件的目录,用法是“export LD_LIBRARY_PATH=目录1:目录2:目录3:.”,目录之间用半角的冒号分隔,最后的圆点指当前目录。接下来修改动态库中func函数的代码:

// printf("我心匪石,不可转也。我心匪席,不可卷也。威仪棣棣,不可选也。\n");
printf("生活美好如鲜花,不懂享受是傻瓜;\n");

如下重新编译动态库,无需重新编译book265,直接执行程序。

g++ -fPIC -shared -o libpublic.so public.cpp
./book265
生活美好如鲜花,不懂享受是傻瓜;

18.3 libc(标准):gnu libc(glibc)(实现)

编译【预处理(语法检查),编译(.c->.s汇编文件),汇编(.s->.o二进制文件),链接(多个.o合并成1个执行文件)】的最后阶段将依赖引入过程叫链接:so文件通过mmap加载进内存,动态链接的a.out文件小且内存占用小,此外动态链接在so库更新后不需重新编译,一般首选。很多进程用到C语言libc.so里stdio.h里打印函数,如果通过静态链接,这样占用的内存多。
在这里插入图片描述
在这里插入图片描述
static指定静态链接。gcc是gnu的编译工具集合,gcc不光编译c语言且支持很多平台
在这里插入图片描述
如下系统没有glibc库。
在这里插入图片描述

18.4 编译时为什么要加上 –lm ?:man exp:Link with -lm

// 代码一
#include <stdio.h>
#include <math.h> //exp
int main(int argc, char const *argv[]){
   printf("The exponential value of %lf is %lf\n", 0, exp(0));
   printf("The exponential value of %lf is %lf\n", 0+1, exp(0+1)); //e的1次幂
   printf("The exponential value of %lf is %lf\n", 0+2, exp(0+2));
   return(0);
}

在这里插入图片描述

// 代码二
#include <stdio.h>
#include <math.h>
int main(int argc, char const *argv[]){
   double x = 0;
   printf("The exponential value of %lf is %lf\n", x, exp(x));
   printf("The exponential value of %lf is %lf\n", x+1, exp(x+1));
   printf("The exponential value of %lf is %lf\n", x+2, exp(x+2));
   return(0);
}

在这里插入图片描述
在这里插入图片描述
代码一调用exp传入的参数是常量为0 。代码二调用exp传入的参数是变量 x,代码一会不会在运行之前就计算好了呢?如下代码一没有看到调用exp的身影,当传入参数为常量时就已计算好了值,最后不需调用exp函数。代码二通过如下main.s汇编代码可见多次调用call函数。
在这里插入图片描述
math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,程序中用到的数学函数要到这个库文件里找。

gcc a.c -o a.outarm-linux-gcc a.c -o b.out,如果执行out文件出现No such file or directory,则将如下两个so文件互相ln -s建软链接。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农编程录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值