编译时与运行时的区别

2025年9月5日 | 阅读 9 分钟

在 C 编程中,编译时和运行时是软件开发中使用的两个术语。编译时是指将源代码转换为可执行代码的时间,而运行时是指可执行代码开始运行的时间。

Difference between Compile Time and Runtime

编译时和运行时都指的是不同类型的错误。让我们先理解编译时和运行时的概念。之后,我们将探讨它们之间的区别。

什么是编译时错误?

在 C 编程语言中,编译时错误是在我们编写了任何编程语言的错误语法或语义时发生的错误。这些错误在程序编译期间由编译器找到。在程序中删除所有错误之前,编译器不允许程序运行。当程序中所有错误都被删除后,编译器将生成可执行文件。

编译器错误类型

C 编程中有两种类型的编译时错误。这些错误如下:

Difference between Compile Time and Runtime

在这里,我们将一一讨论这些编译时错误。

1) 语法错误

在 C 编程中,语法错误是当我们不遵循编程语言的正确语法时编译器抛出的错误类型。

例如

说明

上述声明会生成编译时错误,因为在 C 编程语言中,每个语句都以分号 (;) 结尾。这里,我们在语句末尾使用了冒号 (:) 而不是分号,这会引发语法错误。

2) 语义错误

在 C 编程中,语义错误是在语法正确但逻辑或语句错误时发生的错误类型。编译器无法检测到此错误,但程序会生成不正确的结果。

例如

说明

上述语句会引发编译时错误,因为我们将 'c' 的值赋给 'a' 和 'b' 的和,这在 C 编程语言中是不可能的。C 编程只能在赋值运算符的左侧包含一个变量,而在 赋值运算符 的右侧可以包含多个变量。

因此,我们可以将上述语句重写为:

什么是运行时错误?

在 C 编程中,运行时错误是指在执行时发生的错误,但代码已成功编译,并且编译器无法捕获该错误。运行时错误的一个已知示例如除以零等。这些运行时错误不容易检测,因为编译器不会指出这些错误,因此代码可以成功编译。

运行时错误类型

让我们探讨一些典型的 C 运行时错误类型、情况及其可能的影响。

Difference between Compile Time and Runtime

在这里,我们将一一讨论这些运行时错误。

1) 除以零

由于除以零在数学上是不可定义的,因此尝试将整数除以零会导致运行时错误。此错误会导致程序崩溃或产生异常。

使用除以零的 C 运行时错误

让我们举例说明 C 编程中除以零的运行时错误。

示例

编译并运行

输出

Floating point exception 

说明

在此示例中,我们取了两个整数值 x 和 y 并为它们赋值。之后,我们还初始化了另一个整数变量(result)来保存除法结果,其中我们进行了 x/y 的除法。最后,我们打印了输出结果。在输出中,我们可以看到一个“浮点异常”错误消息,当程序因除以零而遇到运行时问题时会产生此消息。

2) 访问数组越界

当数组元素被访问超出特定限制时,就会发生运行时错误。当索引大于数组大小时,并且内存访问规则被违反时,就会发生错误。

通过访问数组越界发生的 C 运行时错误

让我们举例说明 C 中通过访问数组越界发生的运行时错误。

示例

编译并运行

输出

Value: 1529283018
Segmentation fault (core dumped)

说明

在此示例中,我们有一个数组 arr,其有效索引从 0 到 4,但代码尝试访问 arr[10],这超出了数组的边界。它显示了未定义行为的结果,这意味着程序可能会打印垃圾值、崩溃或行为不可预测,因为 C 不执行自动边界检查。

3) 空指针解引用

当我们尝试访问空指针的内存地址时发生的运行时错误,这被称为解引用 空指针。访问空指针会导致不可预测的行为,因为它们不指向合法的内存位置。

使用空指针解引用的 C 运行时错误示例

让我们举例说明 C 中使用空指针解引用的运行时错误。

示例

编译并运行

输出

Segmentation fault (core dumped)

说明

在此示例中,我们初始化了一个整数指针变量 *ptr,并将其创建为 NULL 指针。在下一个初始化的整数变量中,我们尝试解引用 NULL 指针。之后,我们打印了输出结果。在输出中,我们可以看到尝试解引用空指针会导致段错误,从而导致程序崩溃并显示错误消息。

4) 堆栈溢出

在 C 编程中,当调用堆栈(包含函数调用信息)增长超出预期时,就会发生 堆栈溢出。当递归函数缺乏适当的终止条件时,通常会导致无限递归。

使用堆栈溢出的 C 运行时错误示例

让我们举例说明 C 中使用堆栈溢出的运行时错误。

示例

编译并运行

输出

Segmentation fault (core dumped)

说明

在此示例中,我们创建了一个 recursiveFunction() 方法,在该方法中,我们进行了没有终止条件的递归调用,然后退出该方法。之后,我们创建了 main() 方法,并在其中调用了 recursiveFunction()。在输出中,我们可以看到程序开始无限递归,导致堆栈溢出并发生段错误。

5) 未初始化变量

众所周知,未初始化变量具有未定义的值,这意味着如果我们声明一个变量但在使用前未初始化它,它将包含一个未定义的值。程序可能会提供令人惊讶的结果或崩溃,具体取决于情况。

使用未初始化变量的 C 运行时错误示例

让我们举例说明 C 中使用未初始化变量的运行时错误。

示例

编译并运行

输出

Value: 32765
Value: 32767
Value: 32764
Some random value (varies each time)

说明

在此示例中,我们在 main() 函数中声明了一个整数变量。之后,我们尝试打印声明变量的值。在输出中,我们可以看到结果值是随机值。这是因为未初始化变量的值可以是从分配给该变量的内存区域中选择的任何随机值。

编译时与运行时错误的主要区别

C 编程中的编译时错误和运行时错误之间存在一些主要区别。一些主要区别如下:

特点编译时运行时
定义编译时错误是在编译时产生的错误,由编译器检测到。运行时错误不是由编译器生成的,并且在执行时会产生不可预测的结果。
错误检测如果编译器在程序中检测到错误,它将阻止代码编译。编译器在编译时不会检测到错误;程序编译成功,但在执行时会引发运行时错误。
示例它包含语法、优化和语义错误,例如语句末尾缺少分号或类型不匹配。它包含除以零、计算负数平方根、段错误和无限循环等错误。
涉及它涉及 gcc 等工具,gcc 是一个 C 编译器。它涉及操作系统和运行时环境。
错误修复它必须在程序执行之前修复。它可能需要在执行期间进行错误处理(通过错误检查或异常处理)。

编译时和运行时示例

让我们举多个例子来说明 C 中编译时错误和运行时错误之间的区别。

编译时错误示例

让我们举例说明 C 中的编译时错误。

示例

编译并运行

输出

ERROR!
/tmp/05CinY1EAB/main.c:4:17: error: expected ',' or ';' before ':' token
    4 |     char s = '3':
      |     

说明

在此示例中,我们将字符值赋给了字符变量 's',然后在打印 's' 的值时,它会引发错误。这是因为我们在语句末尾放了冒号 (:) 而不是分号,所以此代码会生成编译时错误。

C 运行时错误示例

让我们举例说明 C 编程中的运行时错误。

示例

编译并运行

输出

Floating point exception

说明

在此示例中,我们尝试将整数 x(12) 除以 y(0),这会导致运行时错误,因为在 C 中除以零是未定义的。但是,程序会成功编译,但在执行时会崩溃或产生不可预测的行为。

结论

总之,编译时错误发生在编译时,并阻止程序执行,通常由语法、语义或类型相关错误引起。另一方面,运行时错误是指在执行时发生的错误,但代码已成功编译,并且编译器无法捕获该错误。为了确保程序正常可靠地运行,应识别和解决这两种类型的错误。

编译时与运行时常见问题解答

1) 在 C 中,我们可以在编译时检测到运行时错误吗?

不,我们无法在编译时检测到运行时错误。编译器在编译阶段不会检测到该错误,仅在代码执行时才会捕获。

2) 我们如何调试 C 中的编译时和运行时错误?

编译时错误:此类错误通常由编译器报告,并附带行号和错误消息。我们可以仔细阅读这些消息并尝试修复可能导致编译时错误的(如语法错误和类型不匹配)问题。

运行时错误:此类错误在代码执行期间报告。要调试运行时错误,我们可以使用调试工具,如 'gdb',添加打印语句,或使用内存检查器,如 'valgrind' 来确定运行时错误的根本原因。

3) C 中的优化发生在编译时还是运行时?

大多数优化是由编译器在编译时完成的。然而,根据环境,可能会发生一些运行时优化。

4) 编译时和运行时内存分配有什么区别?

编译时内存分配主要用于静态变量和固定大小的数组,而运行时内存分配使用 malloc() 等函数在程序执行期间分配动态内存。

5) C 程序可以在没有编译时的情况下运行吗?

不,在 C 编程语言中,源代码必须在运行之前编译成可执行代码。因此,编译时对于将人类可读代码转换为机器代码很重要。