C语言程序环境和预处理

编译器设计-符号表-中间代码生成 编译器设计-符号表-中间代码生成 Compiler Design - Symbol Table Compiler - Intermediate Code Generation 一.Compiler Design - Symbol Table 符号表是编译器为存储变量名、函数名、对象、类、接口等各种实体的出现情况而创建和维护的一种重要的数据结构。符号表既可用于编译器的分析部分,也可用于编译器的综合部分。 符号表可用于以下目的,具体取决于所使用的语言: 将所有实体的名称以结构化形式存储在一个位置。 以验证是否已 阅读详情

一、程序的翻译环境和执行环境

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

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。

第2种是执行环境,它用于实际执行代码

计算机只能够识别和执行二进制指令,但是我们写出的C语言代码是文本信息,计算机不能直接理解,所以需要在翻译环境把 C语言的代码转换成二进制的指令(可执行程序),然后在执行环境执行二进制的代码。

  1. 翻译环境

翻译环境有两个大的步骤:编译和链接,组成一个程序的每个源文件首先通过编译过程分别转换成目标代码(object code),再由链接器把每个目标文件(linker)捆绑在一起,形成一个单一而完整的可执行程序(exe)。

在这里插入图片描述

目标代码已经是二进制代码,但还不能直接执行,需要链接器引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

  1. 执行环境

可执行程序在执行环境中进行执行,程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

  2. 程序的执行便开始。接着便调用main函数。

  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

  4. 终止程序。正常终止main函数;也有可能是意外终止。

二、C语言程序的编译+链接

在这里插入图片描述

1. 编译的具体过程

预处理

预处理阶段通常会完成以下操作:

  • 注释的删除
  • #define 定义的符号、宏的替换以及删除。
  • 各种条件编译的处理。
  • 头文件的展开:将头文件中的代码展开到到当前代码中。

下面通过一段代码预处理前后的对比就可以看出发生了哪些变化:

预处理前:

#include <stdio.h>            //包含的头文件
#define N 5                   //define定义的一个符号
#define ADD(m,n) ((m)+(n))    //define定义的一个宏
int main()
{
	int a = 0;
	int b = 0;
	int c = N + ADD(2, 3);
	printf("%d\n", c);
	return 0;
}

预处理后:

{
   此代码块表示头文件stdio.h 中的代码,太多了,不方便展示
}

int main()
{
	int a = 0;
	int b = 0;
	int c = 5 + ((2)+(3));
	printf("%d\n", c);
	return 0;
}

编译

**编译阶段(Compilation)**是C语言编译过程中的一个重要阶段。在这个阶段中,编译器将预处理后的C语言代码转换为汇编语言。这个阶段主要包括以下几个步骤:

  1. 词法分析

    在这个步骤中,编译器会将源代码分解成一系列的标记(tokens)。一个标记可以是一个关键字,一个标识符,一个常量,一个符号,等等。例如,下面这行C语言代码:

    int sum = a + b;
    

    会被分解为以下几个标记:int, sum, =, a, +, b, ;。

  2. 语法分析

    语法分析阶段也叫做解析阶段。在这个阶段中,编译器会根据语言的语法规则检查标记的组合是否有效。例如,C语言不允许一个变量被定义两次,所以以下的代码会在语法分析阶段被标记为无效:

    int a;
    int a;
    
  3. 语义分析

    语义分析阶段是检查源代码是否有语义错误的阶段。比如,尝试将一个字符串赋值给一个整数类型的变量,或者使用未定义的变量,这些都是语义错误。

    int a = "hello";  // 语义错误
    b = 10;           // 语义错误,因为b未定义
    
  4. 符号汇总:

    符号汇总阶段会将我们代码中的全局的符号全部汇总起来,比如全局变量名、函数名;符号汇总不会将局部的变量名汇总进来,因为局部变量只有当程序运行起来,进入该变量所在的局部范围时才会被创建,而编译是在编译阶段进行的。

汇编

在汇编阶段,由编译器生成的低级代码(汇编代码)被转换为可以被机器直接解读的机器语言代码。

在这里插入图片描述

汇编阶段的一个重要步骤:符号表创建

编译器会创建一个符号表,这是一个数据库,存储了源代码中定义的所有标识符的信息。例如标识符的类型(是变量还是函数),数据类型(如int,float等),作用域(全局或局部)等。

我们知道,每一个源文件都会单独经过编译器编译生成目标文件,在上面代码中,add.c 经过预处理、编译后形成 add.s,并且会汇总 Add 符号;test.c 经过预处理、编译后形成 test.s,并且会汇总 main 、Add (在test.c函数中声明了Add函数) 符号。

然后,add.s 经过汇编会生成 add.o 文件,test.s 经过汇编会生成 test.o 文件(Linux下,Windows下为 .obj 文件)。

在汇编过程中,add.s 和 test.s 都会单独生成自己的符号表,所谓的符号表其实就是把 Add main 这些符号与一个地址相关联;add.s 中的 Add 符号与一个地址相关联,test.s 中的 main与一个地址相关联、Add 也与一个地址相关联;

这里需要注意:因为 test.s 中的 Add 是函数的声明,编译器不知道 Add 函数是否真的存在,所以 test.s 中与 Add 符号相关联的地址是无效的。

汇编阶段生成的符号表会在链接阶段被使用。

这个阶段的主要任务是将编译器生成的汇编代码转换为机器代码。这个过程通常是一对一的,即每条汇编指令通常会被转换为一条机器指令。然而,这个过程可能会受到目标硬件平台的影响,因为不同的硬件平台可能会有不同的汇编语言和机器语言。

2. 链接的具体过程

在C语言的编译过程中,**链接阶段(Linking Stage)**是最后一个阶段。用于将多个编译后的目标文件(通常为.o文件或.obj文件)和库文件链接成一个单一的可执行程序。以下是链接阶段的主要任务:

  1. 解析外部引用:在一个C程序中,可能会有一些函数或变量是在其他源文件中定义的,这些函数或变量在其他文件中被引用时,被称为外部引用(External References)。在链接阶段,链接器会找到这些外部引用在哪个目标文件或库文件中定义,然后将它们链接起来。
  2. 符号表的合并和重定位:符号表的合并是指编译器会把在汇编阶段生成的多个符号表合并为一个符号表;重定位则是指当同一个符号出现在两个符号表中时,编译器会选取其中和有效地址相关的那一个,舍弃另外一个。
  3. 库链接:如果程序中使用了库函数,链接器会将这些库函数链接到可执行文件中。这些库函数可以是静态库中的函数,也可以是动态库中的函数。静态库在链接阶段被合并入可执行文件,而动态库在程序运行时被加载。
  4. 合并: 最后,链接器将所有的目标文件和库文件合并为一个单一的可执行文件。这个文件包含了程序的所有代码和数据,以及用于操作系统加载和运行程序的信息。

链接过程符号表的合并和重定向的实际意义是非常大的,因为它保证了符号表中的每一个符号都和有效的地址相关联,我们可以通过该地址找到对应符号,可以让我们跨文件地调用函数,使得我们链接生成的 .exe 文件能够被正常执行。

  • 编译器调用函数的规则

在知道了程序编译链接的具体过程之后,我们需要知道编译器调用函数的规则:

首先,编译器会在当前文件中寻找函数的定义,如果找到了,就直接调用函数,在调用函数期间会形成函数的符号表;所以对于定义在本文件内的函数,编译器不需要再去确认符号表中函数的地址,也就不需要进行后续的链接操作;

如果编译器在本文件中没有找到函数的定义,那么编译器就会去寻找函数的声明,找到之后生成一个符号表,并将符号表关联一个无效的地址;这时候,编译器就需要通过后续链接阶段符号表的合并来匹配有效地址,从而实现跨文件调用函数;当然,也有可能合并不到有效的地址,从而在重定位时发生链接型错误;

最后,如果编译器在本文件内既没有找到函数的定义,也没有找到函数的声明,那么编译器就会直接报出编译型错误。


三、详解C语言程序的预处理

C语言的预处理阶段(Preprocessing Stage) 是编译过程中的第一步。在这个阶段,C预处理器会对源代码进行一系列的操作,这些操作主要包括处理预处理指令,如#include, #define, #if,#ifdef等。以下是一些具体的预处理操作:

  1. 包含指令(Include Directives)

#include是一个预处理指令,用于在当前文件中插入指定头文件的内容。例如#include <stdio.h>会将这个头文件的内容插入到当前文件中。这样可以将一些常用的函数声明或者宏定义等放在头文件中,然后在需要的地方通过#include指令来引用。

  1. 宏定义(Macro Definitions)

#define是用于定义宏的预处理指令。例如,#define PI 3.14159会定义一个名为PI的宏,其值是3.14159。在预处理阶段,预处理器会将代码中所有的PI替换为3.14159。

  1. 条件编译(Conditional Compilation)

#if, #ifdef, #ifndef, #else, #elif, #endif等预处理指令用于条件编译。这些指令可以让某些代码块在特定条件下才会被编译。例如,下面的代码只有在DEBUG被定义的情况下才会被编译:

#ifdef DEBUG
    printf("Debugging\n");
#endif

  1. 移除注释

在预处理阶段,预处理器会移除源代码中的所有注释,因为注释对于编译过程来说是无用的。

以上是C语言预处理阶段的主要操作,需要注意的是,预处理只是文本替换操作,预处理器不会检查语法和语义。例如,宏替换只是简单的文本替换,预处理器并不会检查替换后的代码是否合法

1. 预定义符号介绍

C语言中有许多内置的预定义符号,我们可以在程序中直接使用:

__FILE__      // 进行编译的源文件
__LINE__      // 文件当前的行号
__DATE__     // 文件被编译的日期
__TIME__     // 文件被编译的时间
__STDC__    // 如果编译器遵循ANSI C,其值为1,否则未定义
int main()
{
	printf("%s\n", __FILE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%d\n", __LINE__);
	return 0;
}

在这里插入图片描述

2. 预处理指令 #define和#undef

 #define NAME stuff
#define MAX 1000
#define reg register          //为 register 这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写 case 语句的时候自动把 break 写上

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

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

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

注意事项

  • #define 定义的标识符会在程序的预处理阶段被全部替换掉。
  • 用 #define 定义标识符的时候,不要在末尾加上分号,避免造成程序逻辑的错误。

3. #define定义宏

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

宏的声明方式如下:

#define name( parament-list ) stuff
# 其中的 parament-list 是一个由逗号隔开的参数列表,stuff是宏的功能主体

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

  • #define定义宏的易错点

以下是两种不完善的定义,在某些情况下可能会出错:

#define SQUARE(x) x * x
#define DOUBLE(x) (x) + (x) 

通过以下代码来调用它们会出现与我们预期不符的结果

#define SQUARE(x) x * x
#define DOUBLE(x) (x) + (x)

int main()
{
	printf("%d\n", SQUARE(3 + 1));
    printf("%d\n", 10 * DOUBLE(3 + 1));
	return 0;
}

在这里插入图片描述

因为以上代码被替换成了如下形式:

#define SQUARE(x) x * x
#define DOUBLE(x) (x) + (x)

int main()
{
	printf("%d\n", 3 + 1 * 3 + 1);
    printf("%d\n", 10 * (3 + 1) + (3 + 1));
	return 0;
}

正确的,万无一失的宏定义应该是如下形式:

#define SQUARE(x) ((x) * (x))
#define DOUBLE(x) ((x) + (x)) 

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

  • #define替换规则

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

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先

被替换。

  1. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

  2. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上

述处理过程。

  • 注意事项
  1. 宏参数和#define 定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归。

  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

4. 预处理操作符#和##的介绍

我们以下面的代码为例介绍 # 的作用:

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

对于上面的代码,我们发现 printf 函数里面的内容有一些冗余,可不可以封装一个函数或者一个宏,来实现既可以打印 a,也可以打印 b?首先,函数是做不到的,因为字符串中的a和b在函数中是固定的,我们只能改变打印的值的大小;而宏就可以利用 # 把参数插入到字符串:

#define PRINT(n) printf("The value of "#n" is %d\n", n)

int main()
{
	int a = 10;
	PRINT(a);
	int b = 20;
	PRINT(b);
	return 0;
}

在这里插入图片描述

我们以下面的代码为例介绍 ## 的作用:

#define CLA(class, num) class##num

int main()
{
	int class001 = 100;
	printf("%d\n", CLA(class, 001));
	return 0;
}

在这里插入图片描述

可以看出 ## 的作用就是:可以把位于它两边的符号合成一个符号

5. 带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果,副作用就是表达式求值的时候出现的永久性效果。

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )

int main()
{
    int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?
    return 0;
}

这里我们得知道预处理器处理之后的结果是什么:

z = ( (x++) > (y++) ? (x++) : (y++));

在这里插入图片描述

所以上面的代码不仅不能得到我们想要的 z 的值,还会让 x 和 y 的值变得不可控

6. 宏和函数的对比

宏通常被应用于执行简单的运算

原因有以下两点:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。

  2. 更为重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。反之宏是类型无关的,可以适用于整型、长整型、浮点型等可以用 > 来比较的类型

而复杂、代码量大的运算通常由函数来完成,因为宏和函数相比有很多劣势:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,使用宏的地方少,否则可能大幅度增加程序的长度。

  2. 宏是没法调试的。

  3. 宏由于类型无关,也就不够严谨

  4. 宏可能会带来运算符优先级的问题,导致程容易出现错

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

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

//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

宏和函数的对比

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码。
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些。
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测、
带有副作用的参数参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
调试宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

函数和宏的命名约定

由于函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者; 为了区分宏和函数,我们平时的一个命名习惯是:

  • 宏名全部大写。
  • 函数名不要全部大写。

7. 命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程。例如:当我们想根据同一个源文件编译出不同的版本的时候,就可以使用命令行定义。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大些,我们需要一个数组能够大些。)

以下是一个例子:

#include <stdio.h>
int main()
{
    int array[ARRAY_SIZE];//ARRAY_SIZE未定义,需要在编译阶段定义
    int i = 0;
    for (i = 0; i < ARRAY_SIZE; i++)
    {
        array[i] = i;
    }
    for (i = 0; i < ARRAY_SIZE; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
    return 0;
}

编译指令:

//linux 环境演示
gcc -D ARRAY_SIZE=10 programe.c

8. 条件编译

在编译一个程序的时候,我们如果要将一条语句或一组语句编译或者放弃,我们就可以使用条件编译指令。比如说:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性地编译。

#include <stdio.h>
#define __DEBUG__
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
#ifdef __DEBUG__ 
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

常见条件编译指令:

1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
    
2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
    
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
    
4.嵌套指令
#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

9. 预处理指令 #include

我们已经知道, #include 指令可以使另外一个文件被编译,就像它实际出现于 #include 指令的地方一样。

这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换,这样一个源文件被包含10次,那就实际被编译10次。

  • 头文件被包含的方式

我们常用的文件包含方式有两种:库文件包含和本地文件包含。

库文件包含形式:#include <filename>

查找策略:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

本地文件包含形式:#include “filename”

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准路径下去查找,如果找不到就提示编译错误。

所以实际上对于库文件也可以使用 “ ” 的形式包含,但是这样做查找的效率就低一些,并且也不容易区分库文件和本地文件

  • 嵌套文件包含

有些时候我们的程序中难免会出现同一个头文件被重复包含的情况,比如一个 stdio.h 头文件被包含多次,这时我们可以使用条件编译来避免头文件被重复包含:

#ifndef __TEST_H__
#define __TEST_H__
//包含头文件
#endif   //__TEST_H__

我们更常用的是在头文件中加入这句话来防止重复包含:#pragma once

四、总结

掌握C语言程序编译过程对于程序员来说是非常重要的,虽然它不能直接帮助我们提高代码能力,但是它能帮助我们更好地理解和调试代码:

通过了解编译过程,程序员可以深入了解代码是如何被转换成机器码并在计算机上执行的,就可以通过优化算法、数据结构和代码结构,以及使用编译器选项进行代码优化,改善代码的运行速度和内存使用

了解编译过程还可以帮助程序员理解错误和警告信息的含义,并且更好地进行代码调试

【Xilinx】如何在petalinux下编译vcu-gst-app以及独立编译vcu-gst-app Xilinx从2018.1开始,提供了一套基于zcu106的vcu参考设计。里面提供了一套基于GStreamer的程序,即vcu-gst-app。如何脱离petalinux直接编译呢,这里以为例,其他版本也是类似的操作。 阅读详情

相关推荐

【小白指南】如何将deepseek引入WPS\Word\Excel中去?

如何将deepseek引入WPS\Word\Excel中去?纯小白都能看的懂,一步步操作就能行

2503_90498422的博客 1万+

编译原理-符号表管理

编译原理--符号表

weixin_42286658的博客 5538

CST Studio Suite安装详细教程(附安装包)CST Studio Suite 2024超详细安装教程

CST Studio Suite 2024安装指南摘要:本文详细介绍了CST Studio Suite 2024电磁仿真软件的下载与安装流程,包含23个图文步骤说明,从解压安装包到配置许可证服务器。重点提示了以管理员身份运行、自定义安装路径等关键步骤,并提供了常见问题解决方案和入门技巧。该软件适用于天线设计、PCB分析等高频电磁场仿真,安装后需通过localhost:27000连接许可证服务器完成激活。

pythonqiang9的博客 1万+

5. C++第一节到第四节总结

一、符号表 什么是符号表?符号表存储在程序中的哪个地方? 1. 符号表是编译器在编译过程中产生的关于源程序中语法符号的数据结构; 2. 如常量表、变量名表、数组名表、函数名表等等; 3. 符号表是编译器自用的内部数据结构; 4. 符号表不会进入最终产生的可执行程序中;...

zhubao124的博客 735

人工智能面试总结-分类与聚类

目录说说常用的分类算法,以及各自的优缺点?说说为什么正确率有时不能有效评估分类算法?说说什么样的分类器最好?说说聚类算法有哪些衡量标准?(选看)各聚类算法性能比较汇总说说什么是聚类,你知道哪些聚类算法?具体展开介绍你知道的聚类算法?说说K-Means聚类算法流程?说说K-Means聚类算法如何调优?说说K-Means聚类算法如何选择初始点?说说K-Means聚类聚的是特征还是样本?说说K-Means聚类的特征距离是如何计算的?说说聚类与分类的区别?手撕K-Means聚类 说说常用的分类算法,以及各自的优缺点

啥都生的博客 3862

YOLOv5改进 | 注意力篇 | iRMB倒置残差块注意力机制(轻量化注意力机制,附二次创新)

本文给家大家带来的改进机制是iRMB,其是在论文Rethinking Mobile Block for Efficient Attention-based Models种提出,论文提出了一个新的主干网络EMO(后面我也会教大家如何使用该主干,本文先教大家使用该文中提出的注意力机制)。其主要思想是将轻量级的CNN架构与基于注意力的模型结构相结合(有点类似ACmix)我将iRMB和C2f结合,然后也将其用在了检测头种进行尝试三种结果进行对比,针对的作用也不相同,但是无论那种实验均有一定涨点效果。

Snu77的博客 2570

c语言学习总结——程序环境

c语言学习总结分享

weixin_69519040的博客 591

OpenClaw进阶实战(五):多模态能力扩展——让OpenClaw看懂图片、听懂语音

本文介绍了如何在OpenClaw中实现多模态能力,包括图像文字识别(OCR)、图像描述和语音识别(ASR)三种技能。通过PaddleOCR、BLIP和Whisper等开源工具,可以本地化部署这些功能,保护数据隐私。文章详细展示了三个独立技能模块的实现代码,并提出了一个"多模态智能管家"方案,能自动识别消息类型(文本/图片/语音)并调用相应处理技能。该方案适用于电商客服、会议纪要等真实业务场景,使AI助手具备"眼睛"和"耳朵"的多模态交互能力。

PeterPan的博客 160

操作系统系列:快速了解C程序的编译及运行

本文介绍C语言编译器及Unix系统调用。

无所适从的我的博客 1316

手把手教你搭建工业级C#上位机:完整架构+通信+配置+日志,新手也能落地(附全套源码)

模型层是“数据容器”,统一数据格式,避免参数传递混乱。本文搭建的C#上位机,采用“分层+模块化”架构,解耦了UI、通信、配置、日志四大核心功能,不仅满足工业场景的基础需求,还为后续扩展打下基础。新手跟着步骤走,既能掌握上位机的核心开发技能,又能理解工业级项目的架构设计思路。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1154

EtherCAT ROS2 入门教程(一)

本文介绍了EtherCAT驱动与ROS2的集成方案,主要包括三部分内容:1)在Ubuntu 22.04上安装IgH EtherCAT主站的详细步骤,包括内核配置、源码编译和网络适配器设置;2)构建ethercat_driver_ros2,通过ros2_control描述文件配置硬件接口和EtherCAT从站模块插件;3)使用方法,包括启动EtherCAT主站和运行配置包。该方案支持通过参数文件定义硬件接口,并提供通用和专用两种EtherCAT模块插件,便于开发基于EtherCAT的工业应用。

机器人控制与仿真应用 1528

C语言之程序环境和预处理

那我们是不是可以写这样的代码?printf;...另外一个技巧是: 使用# ,把一个宏参数变成对应的字符串。比如:int i = 10;int i = 10;int i = 10;int i = 10;int i = 10;;int i = 10;//产生了什么效果?代码中的#VALUE 会预处理器处理为:“VALUE” . 最终的输出的结果应该是:##的作用。

baobao1767640830的博客 1083

Zookeeper+Hadoop+Spark+Flink+Kafka+Hbase+Hive 完全分布式高可用集群搭建(保姆级超详细含图文) 附安装包网盘下载

Hadoop+Spark+Flink+Zookeeper+Kafka+Hbase+Hive完全分布式高可用集群搭建 Hadoop高可用集群搭建 Spark高可用集群搭建 Flink高可用集群搭建 Zookeeper高可用集群搭建 Kafka高可用集群搭建 Hbase高可用集群搭建 Hive高可用集群搭建 mysql搭建 jdk部署 大数据集群环境搭建

Mr.L-OAM的博客 2万+

C语言总结十四:程序环境和预处理详细总结

了解程序的运行环境可以让我们更加清楚的程序的底层运行的每一个步骤和过程,做到心中有数,预处理阶段是在预编译阶段完成,掌握常用的预处理命令语法,可以让我们正确的使用预处理命令,从而提高代码的开发能力和阅读别人代码的能力,本篇博客详细总结C语言中的程序环境和预处理,达到理解并运用的目的!

weixin_47040031的博客 1605

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

C语言的程序环境及其预处理

我们在比较熟悉C语言语法的情况下再来学习一下,C语言的程序环境及其预处理,让我们对C语言的理解更加深入。

qq_63943454的博客 692

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

【C语言进阶17——程序环境和预处理】

程序环境和预处理前言1、程序的翻译环境和执行环境2、详解C语言程序的编译和链接2.1 翻译环境2.2 编译具体阶段2.3 查看编译每一步2.4 程序执行的过程3、预处理3.1 预定义符号3.2 #define3.2.1 #define 定义标识符3.2.2 #define 定义宏3.2.3 #define 替换规则3.2.4 #和##3.2.5 带副作用的宏参数3.2.6 宏和函数对比3.2.7 命名约定3.3 #undef3.4 命令行定义3.5 条件编译3.6 文件包含3.6.1 头文件被包含的方式3.

taibudong1991的博客 3757

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

【C语言】程序环境预处理 -- 详解

程序环境预处理

Elena's Blog 977

C语言-了解程序环境和预处理看这一篇(超详解)

本文对程序环境以及预处理阶段的宏和符号进行详细的讲解,通过此篇的学习我们会对一段代码如何执行,在其中将形成什么文件,产生什么结果有一个深入的了解!

熬夜秃顶中...... 1464

C语言——程序环境和预处理

_FILE__ //进行编译的源文件名字__LINE__ //文件当前的行号__DATE__ //文件被编译的日期__TIME__ //文件被编译的时间__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义注意!!!:是左右各两个下划线_ _FILE_ _这些预定义符号都是语言内置的。int main()//__FILE__是文件名,即字符串//如果显示未定义,则说明当前编译器不支持ANSI C//预处理阶段就会被替换return 0;语法//为。

2301_80342122的博客 721

[C/C++]C语言的程序环境和预处理

本文主要讲述C语言的程序环境和预处理。 一、程序的翻译环境和执行环境 存在源代码转换为可执行的机器指令的翻译环境和实际执行代码的执行环境,两个不同的环境。 1.翻译环境 每个源文件通过编译过程分别转换成目标代码,每个目标文件由链接器捆绑在一起形成可执行程序。过程图解如图: 编译本身也分为下面几个阶段: VIM编译期间的每一步: (1) 预处理 选项gcc -E...

RMA515T的博客 1万+

【C语言】程序环境和预处理

程序的编译环境、程序的执行环境、预处理指令#define、预定义符号介绍、宏和函数的定义、命令定义、预处理指令、条件编译

qq_63474430的博客 1710

C语言:程序环境和预处理

C语言进阶,讲解程序环境和预处理

2501_91709575的博客 1503

C语言进阶---程序环境和预处理

每有欣喜,必有遗憾,不能与你分享,许多事不再那么有意义。

weixin_75128035的博客 1349

C语言中的程序环境和预处理

C语言中的程序环境和预处理

l_shadow_m的博客 1011

C语言程序环境和预处理详解

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境,它用于实际执行代码。在ANSIC的任何一种实现中,存在两个不同的环境。

hy____123的博客 2276

C语言----程序环境和预处理

_FILE__ //进行编译的源文件__LINE__ //文件当前的行号__DATE__ //文件被编译的日期__TIME__ //文件被编译的时间__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义可以尝试运行一下这个#define reg register //为register创建一个更短的名字;)// 形象直观case //自动补上break的case__DATE__,__TIME__ )//定义内容过长时\充当续行符(不能加其他东西)

weixin_73579966的博客 1160
上一篇: C语言小项目——通讯录
下一篇: 【数据结构】顺序表的增删查改
岸边露伴'
博客等级 码龄3年 2粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值