程序环境和预处理详解

news2024/11/28 2:33:57

文章目录

    • 一、程序环境
      • 1.1 - 翻译环境
        • 1.1.1 - 编译
          • 1.1.1.1 - 预编译(预处理)
          • 1.1.1.2 - 编译
          • 1.1.1.3 - 汇编
        • 1.1.2 - 链接
      • 1.2 - 执行环境
    • 二、预处理详解
      • 2.1 - 预定义符号
      • 2.2 - #define
        • 2.2.1 - #define 定义标识符
          • 2.2.1.1 - 语法
          • 2.2.1.2 - 建议
        • 2.2.2 - #define 定义宏
          • 2.2.2.1 - 语法
          • 2.2.2.2 - 示例一
          • 2.2.2.3 - 示例二
        • 2.2.3 - #define 替换规则
        • 2.2.4 - \#
        • 2.2.5 - \##
        • 2.2.6 - 带副作用的宏参数
        • 2.2.7 - 宏和函数的对比
        • 2.2.8 - 命名约定
      • 2.3 - #undef
      • 2.4 - 命令行定义
      • 2.5 - 条件编译
      • 2.6 - 头文件包含
      • 2.6 - 头文件包含

一、程序环境

在 ANSI C 的任何一种实现中,存在两种不同的环境

ANSI C 是由美国国家标准协会(ANSI,即 American National Standards Institute)及国际标准化组织(ISO,即 International Organization for Standardization)推出的关于 C 语言的标准

  1. 第一种是翻译环境(translation environment),在这个环境中源代码被转换为可执行的机器指令。
  2. 第二种是执行环境(execution environment),它用于实际执行代码。

1.1 - 翻译环境

程序的翻译就在翻译环境当中执行,翻译由编译链接过程组成。

在这里插入图片描述

组成一个程序的每个源文件通过编译过程分别转换成目标文件

每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入标准 C 函数库中任何被该程序所用到的函数,而且它可以搜索程序个人的程序库,将其需要的函数链接到程序中

1.1.1 - 编译

编译又具体分为预编译(预处理)、编译、汇编这三个过程。

下面通过 Windows 平台下的 gcc 编译器来演示整个过程

test.h

struct S
{
    char c;
    int i;
};

test.c

#include "test.h"

// This is a test program.

#define MAX 100

int main()
{
    struct S s;
    int m = MAX;
    return 0;
}
1.1.1.1 - 预编译(预处理)

输入指令 gcc -E test.c -o test.i,其中 -E 选项可以使编译在预编译过程结束后停下来,后面的 -o 选项则表示输出(output)的意思,即将预编译结束后源代码所呈现的内容放到 test.i 这个文件中去。

test.i

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "test.c"
# 1 "test.h" 1
struct S
{
    char c;
    int i;
};
# 2 "test.c" 2





int main()
{
    struct S s;
    int m = 100;
    return 0;
}

通过对比 test.ctest.i,我们可以得出在预编译阶段编译器会执行 #include 头文件的包含#define 定义符号的替换以及注释的删除等文本操作。

1.1.1.2 - 编译

输入指令 gcc -S test.c 或者 gcc -S test.i,其中 -S 选项可以使编译在编译过程结束后停下来,在编译过程结束后会生成一个 test.s 的文件。

test.s

	.file	"test.c"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$48, %rsp
	.seh_stackalloc	48
	.seh_endprologue
	call	__main
	movl	$100, -4(%rbp)
	movl	$0, %eax
	addq	$48, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 8.1.0"

在编译阶段编译器会进行语法分析词法分析语义分析符号汇总,将 C 语言代码翻译成汇编代码

1.1.1.3 - 汇编

输入指令 gcc -c test.c 或者 gcc -c test.s,其中 -c 选项可以使编译在汇编过程结束后停下来,在汇编过程结束后会生成一个 test.o目标文件

在 VS 中编译源文件得到的目标文件的后缀名是 .obj

在汇编阶段编译器会生成符号表,将汇编指令翻译成二进制指令

1.1.2 - 链接

链接过程包括

  1. 合并段表
  2. 符号表的合并和重定位
  3. 生成可执行文件

1.2 - 执行环境

在编写和转换一个 C 程序之前,需要考虑它的执行环境,因为这关系到源文件的内容(程序应当如何编写),也关系到转换后的程序是否正常执行。通常有两种不同的执行环境,分别是独立式环境(freestanding environment)宿主式环境(hosted environment)

这两种环境的划分基于 C 在不同领域里的广泛应用。在很多情况下,可能没有操作系统,或者从操作系统那里得到的支持有限,又或者正在编写一个操作系统。此时,要求程序能独立自主地执行。在这方面,典型的例子包括仪器仪表的固件、控制器等嵌入式领域里的设备。

在宿主式环境下,程序的加载、执行和终止通常要受操作系统的控制和调度,并允许使用操作系统提供的各种功能和组件,比如文件系统。

举例来说,如果制作了一个带有处理器的数字电路,并想用 C 编写一个程序来直接驱动它工作(前提是存在一个针对该处理器的 C 的实现,能够生成被该处理器识别和执行的机器代码),那么,这个简单的电路就是独立式环境;如果想写一个能在 Windows 下运行的窗口程序,那么 Windows 连同运行它的硬件就构成了宿主式环境。

以上文字摘自 《标准 C 语言指南》(电子工业出版社)的 1.3.2 节"执行环境"

二、预处理详解

2.1 - 预定义符号

__FILE__  // 进行编译的源文件
__LINE__  // 当前的行号
__DATE__  // 文件被编译的日期
__TIME__  // 文件被编译的时间
__STDC__  // 如果编译器遵循 ANSI C,其值为 1,否则未定义

这些预定义符号都是语言内置的

示例

#include <stdio.h>

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	// printf("%d\n", __STDC__);
	printf("%s\n", __FUNCTION__);
	return 0;
}

一般在文件输入输出以及输出日志等场合下我们会用到这些预定义符号

2.2 - #define

2.2.1 - #define 定义标识符

2.2.1.1 - 语法
#define name stuff

示例 1

#define do_forever for(;;)  // 用更形象的符号来替代一种实现

示例 2

#include <stdio.h>

#define CASE break;case  // 在写 case 语句的时候自动把 break 写上

int main()
{
	int day = 0;
	scanf("%d", &day);
	switch (day)
	{
	case 1:
		printf("星期一\n");
	CASE 2:
		printf("星期二\n");
	CASE 3:
		printf("星期三\n");
	CASE 4:
		printf("星期四\n");
	CASE 5:
		printf("星期五\n");
	CASE 6:
		printf("星期六\n");
	CASE 7:
		printf("星期天\n");
		break;
	default:
		printf("输入错误!\n");
		break;
	}
	return 0;
}

示例 3

// 如果 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都要加上一个反斜杠(续行符)
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
							date:%s\ttime:%s\n", \
							__FILE__, __LINE__, \
							__DATE__, __TIME__)
2.2.1.2 - 建议

在使用 #define 定义标识符时,建议不要在最后加上分号(;),否则容易导致问题的出现。例如

#define MAX 1000;

if (condition)
    max = MAX;  
else
    max = 0;

max = MAX; 实际上是 max = MAX;;,即一条赋值语句和一条空语句

2.2.2 - #define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)

2.2.2.1 - 语法
#define name(parameter-list) stuff

其中参数列表 parameter-list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中

注意:参数列表的左括号必须与 name 紧邻,如果两者之间有任何空白存在, 参数列表就会被解释为 stuff 的一部分

2.2.2.2 - 示例一
#define SQUARE(X) X * X

以上定义的宏存在一定的问题,例如

#include <stdio.h>

#define SQUARE(X) X * X

int main()
{
	printf("%d\n", SQUARE(5 + 1));  // 11
	return 0;
}

程序输出的结果是 11,而不是预期的 36

这是因为替换文本时,参数 X 被替换成了 5 + 1,SQUARE(5 + 1) 实际上就变成了 5 + 1 * 5 + 1

那么可以做如下的改进

#define SQUARE(X) (X) * (X)
2.2.2.3 - 示例二
#define DOUBLE(X) (X) + (X)

尽管示例二吸取了示例一的教训,但以上定义的宏仍然存在一定的问题,例如

#include <stdio.h>

#define DOUBLE(X) (X) + (X)

int main()
{
	printf("%d\n", 10 * DOUBLE(5));  // 55
	return 0;
}

程序输出的结果是 55,而不是预期的 100

这是因为 10 * DOUBLE(5) 实际上是 10 * (5) + (5),而乘法运算先于宏定义中的加法运算

接着做如下的改进

#define DOUBLE(X) ((X) + (X))

总结:用于对数值表达式进行求值的宏定义都应该用上面这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用

2.2.3 - #define 替换规则

在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号,如果包含,它们首先被替换。
  2. 替换文本随后插入到程序中原来文本的位置。对应宏,参数名被它们的值所替换。
  3. 最后,再对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号,如果包含,则重复上述处理过程。

注意事项

  1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号,但是对于宏,不能出现递归
  2. 当预处理器搜索 #define 定义的符号时,字符串常量的内容并不被搜索。

2.2.4 - #

#include <stdio.h>

int main()
{
	int a = 10, b = 20;
	double c = 3.14, d = 6.28;
	printf("The value of a is %d\n", a);
	printf("The value of b is %d\n", b);
	printf("The value of c is %lf\n", c);
	printf("The value of d is %lf\n", d);
	return 0;
}

那么能否通过宏实现相同的功能呢

首先我们看看以下的代码

#include <stdio.h>

int main()
{
	printf("hello ""world!\n");
    // 程序输出 hello world!
    // 等价于:
    // printf("%s%s", "hello ", "world!\n");
	return 0;
}

因此我们了解到的一个技巧是:字符串具有自动连接的特点。

同时需要了解的另外一个技巧是:使用 #,可以把宏参数变成对应的字符串。

此时我们就可以通过宏实现相同的功能了

#include <stdio.h>

#define PRINT(FORMAT, VALUE) printf("the value of "#VALUE" is "FORMAT"\n", VALUE)

int main()
{
	int a = 10, b = 20;
	double c = 3.14, d = 6.28;
	PRINT("%d", a);
	PRINT("%d", b);
	PRINT("%lf", c);
	PRINT("%lf", d);
	return 0;
}

2.2.5 - ##

## 可以在宏定义中把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符

示例

#include <stdio.h>

#define CAT(X, Y) X##Y

int main()
{
	int NewYear = 2023;
	printf("%d\n", CAT(New, Year));  // 2023
	return 0;
}

2.2.6 - 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果

副作用就是表达式求值的时候出现的永久性效果,例如

#include <stdio.h>

int main()
{
	int a = 10;
	int b = a + 1;  // 不带副作用
	printf("%d %d\n", a, b);  // 10 11

	int c = 10;
	int d = ++c;  // 带副作用
	printf("%d %d\n", c, d);  // 11 11
	return 0;
}

使用 MAX 宏可以看到带有副作用的宏参数所引起的问题

#include <stdio.h>

#define MAX(x, y) ((x) > (y) ? (x) : (y) )

int main()
{
	int a = 4;
	int b = 6;
	int m = MAX(a++, b++);
	printf("%d %d %d\n", a, b, m);  // 5 8 7
	return 0;
}

MAX(a++, b++) 实际上是 ((a++) > (b++) ? (a++) : (b++))

2.2.7 - 宏和函数的对比

宏的优点

  1. 宏通常被应用于执行简单的运算,而用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。因此宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型,而宏是类型无关的

宏的缺点

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较段,否则可能大幅度增加程序的长度。
  2. 宏是没办法调试的
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程序容易出错。

宏有时可以做到函数做不到的事情。比如宏的参数可以出现类型,但是函数做不到

示例一

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

#define MALLOC(num, type) (type*)malloc(num * sizeof(type)) 

int main()
{
	int* p = MALLOC(10, int);
	if (NULL == p)
	{
		perror("malloc failed!");
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);
	} 
	printf("\n");
	free(p);
	p = NULL;
	return 0;
}

示例二 - offsetof 宏的实现

#include <stdio.h>

#define OFFSETOF(type, member) (size_t)&(((type*)0)->member)

struct S
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%zd\n", OFFSETOF(struct S, c1));  // 0
	printf("%zd\n", OFFSETOF(struct S, i));  // 4
	printf("%zd\n", OFFSETOF(struct S, c2));  // 8
	return 0;
}

2.2.8 - 命名约定

一般来讲函数和宏的使用方式很相似,语言本身无法帮我们区分二者,因此有一个习惯是:

  1. 宏名全部大小
  2. 函数名不要全部大写

2.3 - #undef

这条指令用于移除一个宏定义

语法

#undef name

示例

#include <stdio.h>

int main()
{
#define MAX 100
	printf("MAX = %d\n", MAX);  // MAX = 100
#undef MAX
	int MAX = 10;
	printf("MAX = %d\n", MAX);  // MAX = 10
	return 0;
}

在一个程序中用完宏定义后,为了防止与后面的标识符冲突,需要取消宏定义

2.4 - 命令行定义

许多 C 的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程。

当我们根据同一个源文件编译一个程序的不同版本时,这个特性特别有用。

例如定义不同长度的数组

int buf[MAX_NUM];

在编译程序时,MAX_NUM 的值可以在命令行中指定。编译指令为

// Linux 环境
gcc -D MAX_NUM=10 test.c

测试源代码

#include <stdio.h>

int main()
{
	int buf[MAX_NUM] = { 0 };
	for (int i = 0; i < MAX_NUM; i++)
	{
		buf[i] = i;
		printf("%d ", buf[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

2.5 - 条件编译

在编译一个程序的时候将一个语句或一组语句编译或放弃是很方便的,因为有条件编译(conditional compile)

比如,调试性的代码,删除可惜,保留又碍事,此时就可以选择性地编译。

#include <stdio.h>

#define __DEBUG__

int main()
{
	int arr[10] = { 0 };
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d ", arr[i]);  // 观察数组是否赋值成功
#endif
	}
	return 0;
}

常见的条件编译指令

// 一、单分支的条件编译
#if 常量表达式
	...
#endif
        
// 例如:
#define __DEGUG__ 1
#if __DEBUG__
	...
#endif
// 二、多分支的条件编译
#if 常量表达式
	...
#elif 常量表达式
    ...
#else
    ...
#endif
// 三、判定是否被定义
#if defined(symbol)
	...
#endif
// 或者
#ifdef symbol
	...
#endif
// 四、判定是否未被定义
#if !defined(symbol)
    ...
#endif
// 或者
#ifndef symbol
	...
#endif
// 五、嵌套指令
#if defined(OS_UNIX)
	#ifdef OPTION1
		unix_version_option1();
	#endif
	#ifdef OPTION2
		unix_version_option2();
	#endif
#elif defined(OS_MSDOS)
	#ifdef OPTION2
		msdos_version_option2();
	#endif
#endif

2.6 - 头文件包含

本地头文件包含

#include "filename.h"
  • 查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。
  • Linux 环境的标准头文件的路径/usr/include

库头文件包含

#include <filename.h>
  • 查找策略:直接去标准路径下查找,如果找不到就提示编译错误。
  • 对于库文件也可以使用 "" 的形式包含,但是会降低查找的效率,也不容易区分包含的头文件是本地头文件还是库头文件了

通过条件编译可以避免头文件的重复包含所导致的问题

#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容
#endif

或者

#pragma once
#endif
// 或者
#ifndef symbol
	...
#endif
// 五、嵌套指令
#if defined(OS_UNIX)
	#ifdef OPTION1
		unix_version_option1();
	#endif
	#ifdef OPTION2
		unix_version_option2();
	#endif
#elif defined(OS_MSDOS)
	#ifdef OPTION2
		msdos_version_option2();
	#endif
#endif

2.6 - 头文件包含

本地头文件包含

#include "filename.h"
  • 查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。
  • Linux 环境的标准头文件的路径/usr/include

库头文件包含

#include <filename.h>
  • 查找策略:直接去标准路径下查找,如果找不到就提示编译错误。
  • 对于库文件也可以使用 "" 的形式包含,但是会降低查找的效率,也不容易区分包含的头文件是本地头文件还是库头文件了

通过条件编译可以避免头文件的重复包含所导致的问题

#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容
#endif

或者

#pragma once

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/345243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI极大地改变了知识创造与分发的逻辑

AI改变了&#xff5e;知识创造、分发的逻辑 细想一下这是恐怖的 已经传导出给教育的压力 趣讲大白话&#xff1a;AI机器人成了随身专家 *********** 1.以前靠秀才创造、分发知识 2.后来是教育体系为主 3.再后&#xff0c;互联网平台聚合和分发 4.将来可能大部分是机器人创造、分…

Xshell和Xftp的下载和在linux虚拟机中的使用

Xshell和Xftp的下载和在linux虚拟机中的使用一、Xshell和Xftp简介XshellXftp二、 Xshell和Xftp下载三、Xshell和Xftp安装Xshell安装Xftp安装四、 Xshell和Xftp使用找到linux虚拟机的ip地址Xshell的使用Xftp的使用一、Xshell和Xftp简介 Xshell Xshell 是一个强大的安全终端模拟…

对灵敏度分析技术进行建模(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

9、MyBatis框架——使用注解开发实现数据库增删改查操作、一级缓存、二级缓存、MyBatis实现分页

目录 一、使用注解开发实现数据库增删改查操作 1、搭建项目 2、使用注解开发操作数据库 二、一级缓存 1、一级缓存失效的情况 三、二级缓存 1、手动开启二级缓存cacheEnabled 2、二级缓存机制 四、MyBatis实现分页 1、配置环境 2、startPage()开启分页 3、PageInfo…

charles+夜神模拟器抓包

1.资料地址: 链接&#xff1a;https://pan.baidu.com/s/1w9qYfFPJcduN4If50ICccw 提取码&#xff1a;a7xa2.安装charles 和夜神模拟器并配置参考地址: https://www.beierblog.com/archives/%E4%BA%B2%E6%B5%8B%E5%AE%8C%E5%85%A8%E5%8F%AF%E8%A1%8Ccharles%E6%8A%93%E5%8C%85%E…

两道链表经典算法题---链表有无环(基础+进阶)

生活就像一盒巧克力&#xff0c;你永远不知道你会得到什么。——《阿甘正传》目前自己粗略的学完数据结构&#xff0c;正在开始刷算法题目。个人觉得算法是一个积累&#xff0c;循序渐进的的过程&#xff0c;需要不断加量&#xff0c;进而达到所谓的质。链表作为数据结构一个重…

全网详解MyBatis-Plus LambdaQueryWrapper的使用说明以及LambdaQueryWrapper和QueryWapper的区别

文章目录1. 文章引言2. 代码演示3. 分析LambdaQueryWrapper3.1 引入LambdaQueryWrapper的原因3.2 LambdaQueryWrapper和QueryWapper的区别4. 重要总结1. 文章引言 今天在公司写代码时&#xff0c;发现同事使用LambdaQueryWrapper来查询数据&#xff0c;而我一直习惯使用QueryW…

没对比没伤害,浙江男不买包包被女友拖拽,深圳男收三个女孩红包

又是一年一度的情人节&#xff0c;虽然这只是一个西方的节日&#xff0c;却被中国的商人们充分利用&#xff0c;也造成了不小的社会矛盾。在今年的情人节里&#xff0c;浙江就发生了一件奇葩的事情&#xff0c;一位女子因不满其男友 不给自己买两万元包包&#xff0c;就在商场里…

Onvif协议如何判断摄像机支持 —— 筑梦之路

有人就问什么是Onvif协议呢&#xff1f; 全称为&#xff1a;Open Network Video Interface Forum.缩写成Onvif。 翻译过来是&#xff1a;开放型网络视频接口论坛&#xff0c;目的是确保不同安防厂商的视频产品能够具有互通性&#xff0c;这样对整体安防行业才是良性发展。 现…

【C语言编译器】01程序-编译器-IDE

目录一、程序的几个基本概念二、什么是编译器三、集成开发环境3.1 IDE简介3.2 windows 下的C语言IDE一、程序的几个基本概念 计算机程序&#xff08;Computer Program&#xff09;&#xff1b;港、台译做电脑程式。计算机程序是一组计算机能识别和执行的指令&#xff0c;运行于…

5 款最好的免费 SSD 数据恢复软件

SSD&#xff08;固态硬盘&#xff09;提供比传统硬盘更快的读/写速度&#xff0c;使启动、软件加载和游戏启动更快。因此&#xff0c;在我们选择存储设备时&#xff0c;它是一个极好的选择。但是&#xff0c;它仍然存在数据丢失的风险。假设您是受害者之一&#xff0c;正在寻找…

SpringBoot的创建和使用

SpringBoot是什么&#xff1f;SpringBoot诞生的目的就是为了简化Spring开发&#xff0c;而相对于Spring&#xff0c;SpringBoot算是一个很大的升级&#xff0c;就如同汽车手动挡变成了自动挡。Spring&#xff1a;SpringBoot&#xff1a;SpringBoot的优点SpringBoot让Spring开发…

[技术选型] ClickHouse和StarRocks的介绍

文章目录1.ClickHouse介绍2.StarRocks介绍1.ClickHouse介绍 ClickHouse是面向联机分析处理&#xff08;OLAP&#xff09;的开源分析引擎。最初由俄罗斯第一搜索引擎Yandex开发&#xff0c;于2016年开源&#xff0c;开发语言为C。由于其优良的查询性能&#xff0c;PB级的数据规…

Linux的ACL(扩展权限)规划:setfacl、getfacl

目录 什么是ACL与如何支持启动ACL ACL设置技巧&#xff1a;getfacl、setfacl getfacl命令用法 setfacl命令用法 最简单的【u&#xff1a;账号&#xff1a;权限】设置 使用默认权限设置目录未来文件的ACL权限继承 什么是ACL与如何支持启动ACL ACL是Access Control List的…

【基础篇】7 # 队列:队列在线程池等有限资源池中的应用

说明 【数据结构与算法之美】专栏学习笔记 什么是队列&#xff1f; 队列是一种操作受限的线性表数据结构&#xff0c;特点是先进先出&#xff0c;最基本的操作有&#xff1a;入队 enqueue()&#xff0c;放一个数据到队列尾部&#xff1b;出队 dequeue()&#xff0c;从队列头…

综合保税区快速发展,卖家抓紧瞄准跨境电商

综合保税区指的是我国设立在内陆地区的海关特殊监管区域&#xff0c;具有报税港区的功能&#xff0c;这是由海关参照有关规定对综合保税区进行管理&#xff0c;执行保税港区的外汇政策和税收&#xff0c;集合众多功能于一身&#xff0c;包括保税区、保税物流区、出口加工区、港…

JNI开发之-CMake方式调用第三方so

CMake方式调用第三方so背景CMake工程配置工程配置配置CMakeLists.txt配置build.gradle调用第三方so中的方法背景 最近一个项目是对接自研团队的个so库&#xff0c;因为之前都是用ndk来编译自己的so库&#xff0c;一直没有问题&#xff0c;但是用到这个自研的的so库一直有问题&…

usbmon+tcpdump+wireshark USB抓包

文章目录usbmon抓包及配合wireshark解析usbmon抓包及配合wireshark解析 usbmon首先编译为内核模块&#xff0c;然后通过modprobe usbmon加载到linux sys文件系统中 rootroot-PC:~# modprobe usbmon​ 而后 linux系统下安装 tcpdump rootroot-PC:~# apt-get install tcpdump​…

如何开发一个好用的公共组件

写在前面 当你对某一个业务场景有自己的理解&#xff0c;想提炼开发了一个很好用的组件&#xff0c;想开放给别的同学使用&#xff0c;或者甚至放在社区给任何一个人使用&#xff0c;你应该会产生以下疑问&#xff1a; 一个标准的组件是怎么样的&#xff0c;在开发过程中有哪…

android-java同步方法和异步方法

接口 Java接口是一系列方法的声明&#xff0c;是一些方法特征的集合&#xff0c;一个接口只有方法的特征没有方法的实现&#xff0c;因此这些方法可以在不同的地方被不同的类实现&#xff0c;而这些实现可以具有不同的行为&#xff08;功能&#xff09;。 两种含义&#xff1a…