GCC 指令详解及动态库、静态库的使用

一、GCC

1.1 GCC 介绍

GCC 是 Linux 下的编译工具集,是「GNU Compiler Collection」的缩写,包含 gcc、g++ 等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如 ar、nm 等。

GCC 工具集不仅能编译 C/C++ 语言,其他例如 Objective-C、Pascal、Fortran、Java、Ada 等语言均能进行编译。GCC 还可以根据不同的硬件平台进行编译,即能进行交叉编译,在 A 平台上编译 B 平台的程序,支持常见的 X86、ARM、PowerPC、mips 等,以及 Linux、Windows 等软件平台。

1.2 安装 GCC

首先,查看 gcc 是否安装:

# 查看 gcc 版本
$ gcc -v
$ gcc --version # 查看 g++ 版本
$ g++ -v
$ g++ --version

如果在输入指令后可以获取到 gcc 版本,那么就表明你的 Linux 中已经安装了 gcc:

如果没有安装,则可按照如下方法安装 gcc:

# centos
$ sudo yum update # 更新本地的软件下载列表, 得到最新的下载地址
$ sudo yum install gcc g++ # 通过下载列表中提供的地址下载安装包, 并安装

1.3 GCC 工作流程

1.3.1 一般使用流程

首先准备一个 C 语言代码,并命名为 test.c:

#include <stdio.h>
#define MAX 3 int main()
{
int i;
for (i = 1; i <= MAX; i++)
{
printf("Hello World\n"); // 输出 Hello World
} return 0;
}

一般情况下,我们可以直接通过 $ gcc test.c -o test编译 test.c,并通过$ ./test指令运行生成的可执行文件:

  • -o:output,是 gcc 编译器的可选参数,用于指定输出文件名及路径,默认输出到当前路径下。下图展示了如何通过 -o 参数修改输出路径:

或者不使用 -o 参数,则生成一个默认名称的可执行文件 a.out:

实际上,GCC 编译器在对程序进行编译的时候,分为了四个步骤:

  1. 预处理(Pre-Processing):

    • 在这个阶段主要做了三件事:展开头文件 、宏替换 、去掉注释行
    • 结果得到的还是一个 C 程序,通常是以 .i 作为文件扩展名
  2. 编译(Compiling) :

    • 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码实际要做的工作
    • 在检查无误后,gcc 把代码编译成汇编代码,得到一个以 .s 作为文件拓展名的汇编文件。
  3. 汇编(Assembling):

    + 汇编阶段是把编译阶段生成的 .s 文件转化成目标文件
    + 最终得到一个以 .o 结尾的二进制文件
  4. 链接(Linking):这个阶段需要 GCC 调用链接器对程序需要调用的进行链接,最终得到一个可执行的二进制文件

而 GCC 的编译器可以将这 4 个步骤合并成一个,这也就是为什么我们使用$ gcc test.c -o test就可以直接生成可执行文件 test 的原因。下面我们对这 4 个步骤做个详细的介绍。

1.3.2 详细的工作流程

1.3.2.1 预处理
# 通过添加参数 -E 生成预处理后的 C 文件 test.i
# 必须通过 -o 参数指定输出的文件名
$ gcc -E test.c -o test.i

让我们来观察一下 test.i 中的代码内容(太长了,只观察 main 函数中的替换情况):

int main()
{
int i;
for (i = 1; i <= 3; i++)
{
printf("Hello World\n");
} return 0;
}

通过分析 test.i 可以发现:

  • 宏定义 MAX 被替换为了相应的值 3
  • 注释「// 输出 Hello World」也被去掉了
1.3.2.2 编译
# 通过添加参数 -S 将 test.i 转换为汇编文件 test.s(默认生成 .s 文件)
$ gcc -S test.i
$ gcc -S test.i -o test.s # 写法二
1.3.2.3 汇编
# 通过汇编得到二进制文件 test.o(默认生成 .o 文件,object)
$ gcc -c test.s
$ gcc -c test.s -o test.o # 写法二
1.3.2.4 链接
# 通过链接得到可执行文件 test
$ gcc test.o -o test

在成功生成 test.o 文件后,就进入了链接阶段。在这里涉及到一个重要的概念:函数库。

在 test.c 的代码中,我们通过print()函数打印 Hello World 语句;但是在这段程序中并没有定义 printf 的函数实现,且在预编译中包含进去的「stdio.h」中也只有该函数的声明extern int printf (const char *__restrict __format, ...);,而没有定义函数的实现,那么是在哪里实现的呢?

答案就是:系统把这些函数实现都做到了名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径 /usr/lib64 下进行查找,也就是链接到 libc.so.6 库函数中去,这样就有函数 printf 的实现了,而这也就是链接的作用。

而函数库一般分为静态库和动态库两种:

  • 静态库是指在编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不需要库文件了。在 Linux 中静态库一般以 .a 作为后缀。
  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时链接文件加载库,这样就可以节省系统的开销。在 Linux 中动态库一般以 .so 作为后缀。

如前面所述的 libc.so.6 就是动态库,gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件了。

有关动态库和静态库的详细介绍,将在下文进行具体讲解。

1.3.2.5 总结

最后,通过一张图来总结一下上述流程:

在 Linux 下使用 GCC 编译器编译单个文件十分简单,直接使用$ gcc test.c(test.c 为要编译的 C 语言的源文件),GCC 会自动生成文件名为 a.out 的可执行文件(也可以通过参数 -o 指定生成的文件名);也就是通过一个简单的命令就可以将上边提到的 4 个步骤全部执行完毕了;但是如果想要单步执行也是没问题的。

1.4 GCC 常用参数

下面的表格中列出了一些常用的 gcc 参数,这些参数在 gcc 命令中没有位置要求,只需要编译程序的时候将需要的参数指定出来即可。

gcc 编译选项 解释说明
-E 预处理,主要是进行宏展开等步骤,生成 test.i
-S 编译指定的源文件,但是不进行汇编,生成 test.s
-c 编译、汇编源文件,但是不进行链接,生成 test.o
-o 指定链接的文件名及路径
-g 在编译的时候,生成调试信息,该程序可以被调试器调试
-D 在程序编译的时候,指定一个宏
-std 指定 C 方言,如 -std=c99。gcc 默认的方言是 GNU C
-l 在程序编译的时候,指定使用的库(库的名字一定要掐头去尾,如 libtest.so 变为 test)
-L 在程序编译的时候,指定使用的库的路径
-fpic 生成与位置无关的代码
-shared 生成共享目标文件,通常用在建立动态库时

1.4.1 指定一个宏(-D)

在程序中我们可以通过使用#define定义一个宏,也可以通过宏控制某段代码是否能够被执行。

#include <stdio.h>

int main()
{
int num = 60;
printf("num = %d\n", num);
#ifdef DEBUG
printf("定义了 DEBUG 宏, num++\n");
num++;
#else
printf("未定义 DEBUG 宏, num--\n");
num--;
#endif printf("num = %d\n", num); return 0;
}

由于我们在程序中并没有定义 DEBUG 宏,所以第 8~9 行的代码就不会被执行:

那么如何才能够在程序中不定义 DEBUG 宏的情况下执行第 8~9 行的代码呢?答案是通过 -D 参数:

需要注意的是,-D 参数必须在生成 test.o 前使用(链接前)。如下所示,是无效的:

说了这么多,-D 参数有什么用呢?下面我们简单叙述一下 -D 参数的应用场景。

1.4.1.1 应用场景一

在发布程序的时候,一般都会要求将程序中所有的 log 输出去掉,如果不去掉会影响程序的执行效率,很显然删除这些打印 log 的源代码是一件很麻烦的事情,解决方案是这样的:

  1. 将所有的打印 log 的代码都写到一个宏判定中,可以模仿上边的例子;
  2. 在调试程序的时候指定 -D,就会有 log 输出;
  3. 在发布程序的时候不指定 -D,log 就不会输出;
1.4.1.2 应用场景二

或者,你编写的一个软件,某个付费功能只对已付费的用户 A 开放,但不对白嫖的用户 B 开放,其中一种解决方法是:

  1. 每个用户对应一个维护分支,用户 A 对应 project_1 分支包含付费功能的代码,用户 B 对应的 project_2 分支不包含付费功能的代码。
  2. 当用户 B 付费订阅时,再将付费项目的代码拷贝到 project_2 中

如果再来一个用户 C 呢?有没有感觉很麻烦的样子?那么我们完全可以这样做:

#include <stdio.h>

int main()
{
#ifdef CHARGE
//付费用户执行流程
printf("该用户已付费,执行付费功\n");
#else
//白嫖用户执行流程
printf("白嫖用户,拒绝执行付费功能\n");
#endif printf("公共功能\n"); return 0;
}

在编译付费用户的时候,添加 -D CHARGE 参数;编译白嫖用户,则不添加。这样的话,不管来多少用户,都只需要维护一个分支即可。

1.4.2 指定 C 方言(-std)

对于如下 C 语言代码:

#include <stdio.h>

int main()
{
for (int i = 1; i <= 3; i++)
{
printf("i = %d\n", i);
} return 0;
}

在编译时是会报错的:

但如果我们加上 -std=c99,就可以了:

二、静态库和动态库

2.1 扫盲

库是「已经写好的、供使用的」可复用代码,每个程序都要依赖很多基础的底层库。

从本质上,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。程序中调用的库有两种「静态库和动态库」,所谓的「静态、动态」指的是链接的过程。

2.2 静态库

2.2.1 静态库简介

在 Linux 中静态库以 lib 作为前缀、以 .a 作为后缀,形如 libxxx.a(其中的 xxx 是库的名字,自己指定即可)。静态库以之所以称之为「静态库」,是因为在链接阶段,会将汇编生成的目标文件 .o 与引用的库一起链接到可执行文件中,对应的链接方式称为静态链接。

2.2.2 静态库的生成

在 Linux 中静态库由程序 ar 生成。生成静态库,需要先对源文件进行汇编操作得到二进制格式的目标文件(以 .o 结尾的文件),然后再通过 ar 工具将目标文件打包就可以得到静态库文件了。

使用 ar 工具创建静态库的一般格式为$ ar -rcs libxxx.a 若干原材料(.o文件)

2.2.3 静态库的制作举例

在某目录中有如下源文件,用来实现一个简单的计算器。

add.c

#include <stdio.h>

int add(int a, int b)
{
return a + b;
}

sub.c

#include <stdio.h>

int subtract(int a, int b)
{
return a - b;
}

mult.c

#include <stdio.h>

int multiply(int a, int b)
{
return a * b;
}

具体操作步骤如下:

# 第一步:将源文件 add.c、sub.c、mult.c 进行汇编,得到二进制目标文件 add.o、sub.o、mult.o
$ gcc -c add.c sub.c mult.c # 第二步:将生成的目标文件通过 ar 工具打包生成静态库
$ ar rcs libcalc.a add.o sub.o mult.o

2.2.4 静态库的使用

定义 main 函数如下所示:

main.c

#include <stdio.h>

int main()
{
int a = 20;
int b = 12; printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", subtract(a, b));
printf("a * b = %d\n", multiply(a, b)); return 0;
}

并将静态库 libcalc.a 置于同级目录下:

通过指令$ gcc main.c -o main -L ./ -l calc编译 main.c 文件,并链接静态库 libcalc.a:

  • -L:指定使用的库的路径(因为在同一级目录下,所以可以直接用了./,或者使用绝对路径也是可以的)
  • -l:指定使用的库(库的名字一定要掐头去尾。如:libcalc.a 变为 calc)

编译结果会提示三个 warning,这是由于没有定义这些函数导致的,先暂时不用管。

运行 main 结果如下:

我们思考这么一个问题:由于静态库是我们自己制作的,其所包含的函数我们很清楚,直接链接并使用即可。但如果别人想要使用呢?他们可不清楚静态库中的函数该如何调用,所以我们有必要提供一个头文件,这样将静态库及头文件交给其他人时,他们知道该如何用了。

head.h

#ifndef _HEAD_H_
#define _HEAD_H_ int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); #endif

还记得之前的报错吗?现在有了头文件就要使用起来。

main.c

#include <stdio.h>
#include "head.h" int main()
{
int a = 20;
int b = 12; printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", subtract(a, b));
printf("a * b = %d\n", multiply(a, b)); return 0;
}

编译、链接、运行,一气呵成:

2.2.5 ar 命令参数介绍

制作静态库时所使用的指令$ ar rcs libcalc.a add.o sub.o mult.o div.o共有三个参数:

  • -c:创建一个库,不管库是否存在,都将创建。这个很好理解,就不做过多的解释了。

  • -r:在库中插入(替换)模块 。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。

  • -s:创建目标文件索引,这在创建较大的库时能加快时间。

参数 -r 的详细解释

假设现在有了新的需求,需要静态库 libcalc.a 提供除法运算的功能模块,该怎么操作呢?

首先我们需要新建一个除法运算的源文件 div.c:

#include <stdio.h>

double divide(int a, int b)
{
return (double)a / b;
}

并通过汇编操作生成目标文件 div.o。

接下来我们可以通过 -r 参数将除法运算的模块添加到静态库中:$ ar -r libcalc.a div.o

并且要在 head.h 中增加对除法运算的声明:

#ifndef _HEAD_H_
#define _HEAD_H_ // Other double divide(int a, int b); #endif

参数 -s 的详细解释

在获取一个静态库的时候,我们可以通过$ nm -s libcalc.a来显示库文件中的索引表:

而索引的生成就要归功于 -s 参数了。

如果不需要创建索引,可改成 -S 参数。

如果 libcalc.a 缺少索引,可以使用$ ranlib libcalc.a 指令添加。

2.2.6 其他命令介绍

# 显示库文件中有哪些目标文件,只显示名称
$ ar t libcalc.a # 显示库文件中有哪些目标文件,显示文件名、时间、大小等详细信息
$ ar tv libcalc.a # 显示库文件中的索引表
$ nm -s libcalc.a # 为库文件创建索引表
$ ranlib libcalc.a

2.3 动态库

2.3.1 动态库简介

在 Linux 中动态库以 lib 作为前缀、以 .so 作为后缀,形如 libxxx.so(其中的 xxx 是库的名字,自己指定即可)。相比于静态库,使用动态库的程序,在程序编译时并不会链接到目标代码中,而是在运行时才被载入。不同的应用程序如果调用相同的库,那么在内存中只需要有一份该共享库的实例,避免了空间浪费问题。同时也解决了静态库对程序的更新的依赖,用户只需更新动态库即可。

2.3.2 动态库的生成

生成动态库是直接使用 gcc 命令,并且需要添加 -fpic 以及 -shared 参数:

  • -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
  • -shared 参数的作用是告诉编译器生成一个动态链接库。

2.3.3 动态库的制作举例

还是以上述程序 add.c、sub.c、mult.c 为例:

# 第一步:将源文件 add.c、sub.c、mult.c 进行汇编,得到二进制目标文件 add.o、sub.o、mult.o
$ gcc -c -fpic add.c sub.c mult.c # 第二步:将得到的 .o 文件打包成动态库
$ gcc -shared add.o sub.o mult.o -o libcalc.so # 第三步:发布动态库和头文件
1. 提供头文件 head.h
2. 提供动态库 libcalc.so

至于为什么需要提供头文件,在讲解静态库时已经做了说明,此处不再赘述。

2.3.4 动态库的使用

head.h

#ifndef _HEAD_H_
#define _HEAD_H_ int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); #endif

main.c

#include <stdio.h>
#include "head.h" int main()
{
int a = 20;
int b = 12; printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", subtract(a, b));
printf("a * b = %d\n", multiply(a, b)); return 0;
}

和静态库的链接方式一样,都是通过指令$ gcc main.c -o main -L ./ -l calc来进行链接库操作。

gcc 通过指定的动态库信息生成了可执行程序 main,但是可执行程序运行却提示无法加载到动态库:

./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

这是怎么回事呢?

2.3.5 解决动态库加载失败的问题

首先来看一下不同库的工作原理:

  1. 静态库如何被加载:

    • 在程序编译的最后一个阶段也就是链接阶段,提供的静态库会被打包到可执行程序中。
    • 当可执行程序被执行,静态库中的代码也会一并被加载到内存中,因此不会出现静态库找不到无法被加载的问题。
  2. 动态库如何被加载:
    • 在程序编译的最后一个阶段也就是链接阶段,在 gcc 命令中虽然指定了库路径,但是这个路径并没有被记录到可执行程序中,只是检查了这个路径下的库文件是否存在。同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。
    • 当可执行程序被执行起来之后:
      • 程序会先检测所需的动态库是否可以被加载,加载不到就会提示上边的错误信息。
      • 当动态库中的函数在程序中被调用了,这个时候动态库才加载到内存,如果不被调用就不加载。

动态库的检测和内存加载操作都是由动态链接器来完成的

动态链接器是一个独立于应用程序的进程,属于操作系统。当用户的程序需要加载动态库的时候动态连接器就开始工作了,很显然动态连接器根本就不知道用户通过 gcc 编译程序的时候通过参数 -L 指定的路径。

那么动态链接器是如何搜索某一个动态库的呢,在它内部有一个默认的搜索顺序,按照优先级从高到低的顺序分别是:

  1. 可执行文件内部的 DT_RPATH 段。

  2. 系统的环境变量 LD_LIBRARY_PATH。

  3. 系统动态库的缓存文件 /etc/ld.so.cache。

  4. 存储「静态库 / 动态库」的系统目录 /lib、/usr/lib 等。

按照以上四个顺序,依次搜索,找到之后结束遍历。若检索到最终还是没找到,那么动态连接器就会提示动态库找不到的错误信息。一般情况下,我们都是通过修改系统的环境变量的方式设置动态库的地址。

将动态库路径追加到环境变量 LD_LIBRARY_PATH 中:$ LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:动态库的绝对路径

比如,我所需要的动态库的绝对路径为 /mnt/hgfs/SharedFolders/DynamicLibrary,那么:

$ LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/mnt/hgfs/SharedFolders/DynamicLibrary

这样的话,我在运行 main,就不会报错了。

但是通过这种方式设置的环境变量尽在当前的终端中有效,那么怎样才能让这个设置永久生效呢?

通过指令$ vim ~/.bashrc打开并修改该文件:

修改后,使用$ source ~/.bashrc使修改立即生效。

经过上述操作,就不用每次开启终端都需要修改环境变量了。当然这种永久生效的方式仅适用于动态库路径唯一的情况,如果你每次使用的动态库都在不同的位置,那么这么设置也没啥用

2.4 动态库与静态库的比较

2.4.1 静态库的特点

  1. 静态库对函数库的链接是在编译期完成的。
  2. 静态库在程序编译时会链接到目标代码中,因此使可执行文件变大。
  3. 当链接好静态库后,在程序运行时就不需要静态库了。
  4. 对程序的更新、部署与发布不方便,需要全量更新。
  5. 如果某一个静态库更新了,所有使用它的应用程序都需要重新编译、发布给用户。

2.4.2 动态库的特点

  1. 动态库把对一些库函数的链接载入推迟到程序运行时期。
  2. 可以实现进程之间的资源共享,因此动态库也称为共享库。
  3. 将一些程序升级变得简单,不需要重新编译,属于增量更新。

2.5 使用库的目的

在项目中使用库一般有两个目的:

  1. 为了使程序更加简洁不需要在项目中维护太多的源文件。
  2. 另一方面是为了源代码保密,毕竟不是所有人都想把自己编写的程序开源出来。

当我们拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的 API 函数的声明,也就是头文件,把这些都添加到项目中,就可以快乐的写代码了。

参考资料

GCC 指令详解及动态库、静态库的使用的更多相关文章

  1. #pragma 预处理指令详解

    源地址:http://blog.csdn.net/jx_kingwei/article/details/367312 #pragma  预处理指令详解              在所有的预处理指令中, ...

  2. pragma comment的使用 pragma预处理指令详解

    pragma comment的使用 pragma预处理指令详解   #pragma comment( comment-type [,"commentstring"] ) 该宏放置一 ...

  3. pragma指令详解(转载)

    #pragma comment( comment-type [,"commentstring"] ) 该宏放置一个注释到对象文件或者可执行文件.comment-type是一个预定义 ...

  4. [转]JVM指令详解(上)

    作者:禅楼望月(http://www.cnblogs.com/yaoyinglong) 本文主要记录一些JVM指令,便于记忆与查阅. 一.未归类系列A 此系列暂未归类. 指令码    助记符      ...

  5. Linux 下动态库 / 静态库(依赖)

    一. 依赖动态库的动态库 libfun.so依赖动态库libtest.so(libfun.so动态库里的函数intnothing()调用了libtest.so里的intmytest()函数),而mai ...

  6. GCC参数详解

    GCC参数详解 [介绍] gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步 1.预处理,生成.i的文件 2.将预处理后的文件不转换成汇 ...

  7. Docker技术入门与实战 第二版-学习笔记-3-Dockerfile 指令详解

    前面已经讲解了FROM.RUN指令,还提及了COPY.ADD,接下来学习其他的指令 5.Dockerfile 指令详解 1> COPY 复制文件 格式: COPY  <源路径> .. ...

  8. nginx.conf中关于nginx-rtmp-module配置指令详解

    译序:截至 Jul 8th,2013 官方公布的最新 Nginx RTMP 模块 nginx-rtmp-module 指令详解.指令Corertmp语法:rtmp { ... }上下文:根描述:保存所 ...

  9. C++基础知识之动态库静态库

    一. 静态库与动态库 库(library),一般是一种可执行的二进制格式,被操作系统载入内存执行. 我们通常把一些公用函数制作成函数库,供其它程序使用.函数库分为静态库和动态库 静态库和动态库区别: ...

随机推荐

  1. SpringBean的实例化

    在Spring框架中,想使用Spring容器中的Bean,需要先实例化Bean SpringBean的实例化有3种方式 构造方法实例化 (最常用) 在Java配置类中,写一个构造方法,在这个构造方法中 ...

  2. NC202475 树上子链

    题目链接 题目 题目描述 给定一棵树 T ,树 T 上每个点都有一个权值. 定义一颗树的子链的大小为:这个子链上所有结点的权值和 . 请在树 T 中找出一条最大的子链并输出. 输入描述 第一行输入一个 ...

  3. 算法模板:C++的高精度

    代码是抄别人的:https://blog.csdn.net/code4101/article/details/38705155. 这篇博客只是用来查看保存,非原创. #include<iostr ...

  4. 基于Anacoda搭建虚拟环境cudnn6.0+cuda8.0+python3.6+tensorflow-gpu1.4.0

    !一定要查准cudnn,cuda,tensorflow-gpu对应的版本号再进行安装,且本文一切安装均在虚拟环境中完成. 下文以笔者自己电脑为例,展开安装教程阐述(省略anaconda安装教程): 1 ...

  5. IPV6属于自己专属公网IP

    有了公网IP就可以搭建网站 简单理解公网IP就是私人的服务器 搭建之前一定要注意!没有网络安全意识的不要随便搭建 如何搭建? 材料如下 支持IPV6的光猫 支持IPV6的路由器 支持IPV6的设备 方 ...

  6. 【MySQL】从入门到精通6-MySQL数据类型与官方文档

    上期:[MySQL]从入门到精通5-一对多-外键 这个是官方文档链接,是世界上最全面的MySQL教学了,所有问题都可以在这里找到解决方法. https://dev.mysql.com/doc/ htt ...

  7. mysql 重复执行创建表、新增字段脚本

    #bigint 可重复执行创建表脚本 1 Create Table If Not Exists `库名`.`表名`( 2 字段列表 3 ) ENGINE=InnoDB DEFAULT CHARSET= ...

  8. 网站SQL注入之数字型注入和字符型注入

    什么是SQL注入- (SQL Injection)是一种常见的Web安全漏洞,攻击者利用这个漏洞,可以访问或修改数据,或者利用潜在的数据库漏洞进行攻击.1,是一种将SQL语句插入或添加到应用(用户)的 ...

  9. maven-scope属性

    Maven 中的 scope 属性解释 <dependency> <groupId>org.glassfish.web</groupId> <artifact ...

  10. .NET 反向代理-YARP

    什么是 YARP YARP (另一个反向代理) 设计为一个库,提供核心代理功能,你可以根据应用程序的特定需求进行自定义. YARP 是使用 .NET的基础架构构建在 .NET上的.YARP 的主要不同 ...