首页 > 系统相关 >Memory Leak Detector:C++内存泄漏常见原因分析_2024-07-23_09-29-09.Tex

Memory Leak Detector:C++内存泄漏常见原因分析_2024-07-23_09-29-09.Tex

时间:2024-12-17 21:27:11浏览次数:6  
标签:std 泄漏 释放 07 23 int 09 内存 ptr

Memory Leak Detector:C++内存泄漏常见原因分析

C++内存管理基础

动态内存分配与释放

在C++中,动态内存管理是通过newdelete操作符来实现的。new操作符用于在运行时分配内存,而delete操作符用于释放之前分配的内存。理解动态内存分配与释放的机制对于避免内存泄漏至关重要。

动态内存分配

当你使用new操作符时,C++会在堆上分配内存,并返回一个指向这块内存的指针。例如:

// 动态分配一个int类型的内存
int* p = new int;
*p = 10; // 使用分配的内存

动态内存释放

使用delete操作符可以释放之前通过new分配的内存。如果忘记释放内存,就会导致内存泄漏。例如:

// 释放动态分配的内存
delete p;

释放内存的注意事项

  • 匹配原则:使用new分配的内存必须使用delete来释放,new[]分配的数组必须使用delete[]来释放。
  • 避免重复释放:一旦内存被释放,再次释放同一块内存会导致程序崩溃。

智能指针与RAII

智能指针

智能指针是一种C++模板类,它通过封装指针和其相关的资源管理操作,自动管理动态分配的内存。这包括在智能指针不再被使用时自动释放内存,从而避免内存泄漏。

std::unique_ptr

std::unique_ptr是一个独占所有权的智能指针,它保证了指针的唯一性,意味着一个std::unique_ptr对象拥有它所指向的资源的独占所有权。当std::unique_ptr对象超出作用域时,它会自动释放所管理的资源。

#include <memory>

// 使用std::unique_ptr动态分配一个int类型的内存
std::unique_ptr<int> p(new int);
*p = 10; // 使用分配的内存
// 当p超出作用域时,内存自动释放
std::shared_ptr

std::shared_ptr是一个共享所有权的智能指针,允许多个std::shared_ptr对象共享同一块资源。当最后一个std::shared_ptr对象超出作用域时,资源会被释放。

#include <memory>

// 创建一个std::shared_ptr对象
std::shared_ptr<int> p1(new int);
*p1 = 10;

// 另一个std::shared_ptr对象共享同一块资源
std::shared_ptr<int> p2 = p1;

// 当p1和p2都超出作用域时,内存才被释放

RAII(Resource Acquisition Is Initialization)

RAII是一种编程技术,它将资源的生命周期与对象的生命周期绑定在一起。在C++中,这意味着资源(如内存)在对象构造时被获取,在对象析构时被释放。智能指针是RAII的一个典型应用,它确保了资源的自动管理。

#include <memory>

class MyClass {
public:
    MyClass() {
        // 在构造函数中分配资源
        data = new int;
        *data = 10;
    }

    ~MyClass() {
        // 在析构函数中释放资源
        delete data;
    }

private:
    int* data;
};

// 使用MyClass时,资源在对象创建时分配,在对象销毁时释放
{
    MyClass obj;
    // 使用obj
}
// 资源自动释放

然而,使用智能指针可以更安全地管理资源,避免在析构函数中手动释放内存可能带来的问题,如重复释放。

#include <memory>

class MyClass {
public:
    MyClass() : data(new int) {
        *data = 10;
    }

private:
    std::unique_ptr<int> data;
};

// 使用MyClass时,资源在对象创建时分配,在对象销毁时由std::unique_ptr自动释放
{
    MyClass obj;
    // 使用obj
}
// 资源自动释放,无需手动管理

通过上述示例,我们可以看到C++中动态内存管理的基础,以及如何使用智能指针和RAII原则来更安全、更有效地管理内存,从而避免内存泄漏。

内存泄漏的常见原因

未释放的内存

原理

在C++中,内存泄漏通常发生在程序员分配了内存但忘记释放它的情况下。当一个动态分配的内存块不再被使用时,应该通过调用deletedelete[]来释放它,否则,这部分内存将被持续占用,直到程序结束,这可能导致资源浪费和程序性能下降。

内容

1. 忘记释放内存

示例代码:

#include <iostream>

int main() {
    int* ptr = new int(10); // 分配内存
    // 使用ptr...
    // 忘记释放ptr
    return 0;
}

讲解:

在上述代码中,ptr指向了一块动态分配的内存,但是程序结束时,这块内存没有被释放。这将导致内存泄漏,因为这块内存将不会被其他程序或同一程序的后续执行所使用。

2. 释放内存的代码逻辑错误

示例代码:

#include <iostream>

void func() {
    int* ptr = new int(10);
    // 使用ptr...
    delete ptr; // 正确释放
}

int main() {
    func();
    // 重复调用func(),但每次调用都会分配新的内存块
    // 未在main()中释放所有分配的内存
    return 0;
}

讲解:

虽然func()函数内部正确地释放了内存,但是每次调用func()都会分配新的内存块。如果main()函数或其他调用者没有意识到需要释放这些内存,那么每次调用func()都会导致新的内存泄漏。

资源管理错误

原理

资源管理错误通常涉及对内存的不当管理,包括重复释放、在异常处理中未能释放内存、以及在多线程环境中对内存的不正确同步访问。

内容

1. 重复释放内存

示例代码:

#include <iostream>

void func() {
    int* ptr = new int(10);
    delete ptr; // 正确释放
    delete ptr; // 重复释放,错误
}

int main() {
    func();
    return 0;
}

讲解:

func()函数中,ptr指向的内存被释放了两次。第一次释放是正确的,但是第二次释放会导致未定义行为,因为ptr现在指向的是一块已经被释放的内存。这不仅可能导致内存泄漏(如果ptr被重新分配但没有正确释放),还可能引发程序崩溃。

2. 异常处理中的内存释放

示例代码:

#include <iostream>
#include <new>

void func() {
    int* ptr = nullptr;
    try {
        ptr = new int(10);
        // 使用ptr...
    } catch (std::bad_alloc& e) {
        std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    }
    delete ptr; // 可能尝试释放一个未分配的指针
}

int main() {
    func();
    return 0;
}

讲解:

func()函数中,如果内存分配失败,new操作将抛出一个std::bad_alloc异常。然而,异常处理代码中尝试释放ptr,这可能导致程序崩溃,因为ptr可能仍然为nullptr。正确的做法是在异常处理中检查ptr是否为nullptr,或者使用智能指针(如std::unique_ptr)来自动管理内存。

3. 多线程中的内存管理

示例代码:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int* shared_ptr = nullptr;

void allocateMemory() {
    mtx.lock();
    shared_ptr = new int(10);
    mtx.unlock();
}

void useMemory() {
    mtx.lock();
    // 使用shared_ptr...
    mtx.unlock();
}

void freeMemory() {
    mtx.lock();
    delete shared_ptr;
    mtx.unlock();
}

int main() {
    std::thread t1(allocateMemory);
    std::thread t2(useMemory);
    std::thread t3(freeMemory);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

讲解:

在多线程环境中,shared_ptr被多个线程共享。allocateMemory()线程负责分配内存,useMemory()线程使用内存,而freeMemory()线程负责释放内存。然而,如果线程的执行顺序不当,例如freeMemory()useMemory()之前执行,那么shared_ptr指向的内存可能在使用时已经被释放,导致程序崩溃。使用互斥锁(std::mutex)可以确保线程安全,但是更复杂的同步机制(如条件变量)可能需要来确保内存的正确使用和释放。

总结

内存泄漏和资源管理错误是C++程序中常见的问题,它们可能导致程序性能下降、资源浪费,甚至程序崩溃。通过遵循良好的编程实践,如使用智能指针、异常安全的代码设计,以及在多线程环境中正确使用同步机制,可以有效地避免这些问题。

使用工具检测内存泄漏

Valgrind简介与使用

Valgrind是一个用于内存调试、内存泄漏检测和性能分析的工具框架。它包含多个工具,其中最常用的是Memcheck,用于检测C和C++程序中的内存错误,如未初始化的内存使用、越界访问、重复释放内存以及内存泄漏等。Valgrind通过运行程序的虚拟执行环境来检测这些错误,因此它能够提供详细的内存使用报告。

安装Valgrind

在大多数Linux发行版中,可以通过包管理器安装Valgrind。例如,在Ubuntu上,可以使用以下命令:

sudo apt-get install valgrind

使用Valgrind检测内存泄漏

要使用Valgrind检测内存泄漏,可以运行以下命令:

valgrind --leak-check=yes ./your_program

这里,--leak-check=yes选项指示Valgrind使用Memcheck工具检测内存泄漏。./your_program是你要检测的程序的路径。

示例:使用Valgrind检测C++程序中的内存泄漏

假设我们有以下C++程序,其中包含一个内存泄漏:

// memory_leak_example.cpp
#include <iostream>

int main() {
    int* data = new int[100]; // 分配内存
    for (int i = 0; i < 100; i++) {
        data[i] = i;
    }
    // 忘记释放内存
    std::cout << "Memory leak example." << std::endl;
    return 0;
}

编译这个程序:

g++ -o memory_leak_example memory_leak_example.cpp

然后使用Valgrind检测:

valgrind --leak-check=yes ./memory_leak_example

Valgrind将输出内存泄漏的详细信息,包括泄漏的内存块大小、泄漏的位置以及分配内存的堆栈跟踪。

Visual Studio内存泄漏检测

Visual Studio提供了强大的工具来检测C++程序中的内存泄漏。这些工具包括运行时库检查(CRT检查)和Visual Studio的内存分析器。

启用CRT检查

在Visual Studio中,可以通过以下步骤启用CRT检查:

  1. 打开项目属性。
  2. 转到“配置属性”>“C/C++”>“运行时库”。
  3. 选择“多线程调试DLL”(/MDd)或“多线程调试”(/MTd)。

使用Visual Studio的内存分析器

Visual Studio的内存分析器可以帮助你识别内存泄漏,并提供详细的内存使用情况。要使用它,可以:

  1. 在“分析”菜单中选择“开始内存分析”。
  2. 运行你的程序。
  3. 在“分析”菜单中选择“停止内存分析”。
  4. 查看“内存分析”窗口中的报告。

示例:使用Visual Studio检测C++程序中的内存泄漏

假设我们有以下C++程序,其中包含一个内存泄漏:

// memory_leak_example.cpp
#include <iostream>

int main() {
    int* data = new int[100]; // 分配内存
    for (int i = 0; i < 100; i++) {
        data[i] = i;
    }
    // 忘记释放内存
    std::cout << "Memory leak example." << std::endl;
    return 0;
}

在Visual Studio中,确保CRT检查已启用,然后运行内存分析器。在程序运行结束后,Visual Studio将显示内存泄漏的报告,包括泄漏的内存块大小和泄漏的位置。

总结

使用Valgrind和Visual Studio的内存分析工具可以帮助你有效地检测和定位C++程序中的内存泄漏。通过分析这些工具提供的报告,你可以找到内存泄漏的原因,并采取相应的措施来修复它们。在开发过程中定期使用这些工具,可以确保你的程序具有良好的内存管理,从而提高程序的稳定性和性能。

避免内存泄漏的策略

代码审查与最佳实践

在C++中,内存泄漏通常发生在程序员分配了内存但忘记释放它,或者在内存不再需要时无法正确释放。避免内存泄漏的第一步是遵循良好的代码审查和最佳实践。以下是一些关键点:

1. 使用智能指针

C++11引入了智能指针,如std::unique_ptrstd::shared_ptr,它们在对象不再需要时自动释放内存。这减少了手动管理内存的需求,从而降低了内存泄漏的风险。

示例代码
#include <memory>

class MyClass {
public:
    MyClass() { /* 构造函数 */ }
    ~MyClass() { /* 析构函数 */ }
};

int main() {
    // 使用unique_ptr自动管理内存
    std::unique_ptr<MyClass> myObject(new MyClass());
    // 当myObject超出作用域时,内存将自动释放
    return 0;
}

2. 避免裸指针

裸指针(即没有自动资源管理的指针)容易导致内存泄漏。尽量使用智能指针代替裸指针。

3. 确保资源的正确释放

在使用裸指针时,确保在对象不再需要时调用delete。如果使用new[]分配数组,则使用delete[]释放。

示例代码
class MyClass {
public:
    MyClass() { /* 构造函数 */ }
    ~MyClass() { /* 析构函数 */ }
};

int main() {
    MyClass* myArray = new MyClass[10];
    // 使用delete[]释放数组
    delete[] myArray;
    return 0;
}

4. 使用RAII(Resource Acquisition Is Initialization)原则

RAII是一种编程技术,其中资源(如内存)在对象构造时获取,在对象析构时释放。这确保了即使在异常情况下,资源也能被正确释放。

示例代码
#include <iostream>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired." << std::endl;
    }
    ~Resource() {
        std::cout << "Resource released." << std::endl;
    }
};

void function() {
    Resource res; // 在函数开始时获取资源
    // 函数体
    // 即使函数抛出异常,res的析构函数也会在函数结束时被调用,释放资源
}

int main() {
    try {
        function();
    } catch (...) {
        // 异常处理
    }
    return 0;
}

资源管理技巧

除了代码审查和最佳实践,还有一些资源管理技巧可以帮助避免内存泄漏。

1. 使用容器

C++标准库中的容器,如std::vectorstd::list,内部管理内存,当容器被销毁时,它们会自动释放所有元素的内存。

示例代码
#include <vector>

class MyClass {
public:
    MyClass() { /* 构造函数 */ }
    ~MyClass() { /* 析构函数 */ }
};

int main() {
    std::vector<MyClass> myVector;
    // 添加元素
    myVector.push_back(MyClass());
    // 当myVector超出作用域或被销毁时,所有MyClass对象的内存将自动释放
    return 0;
}

2. 避免循环引用

在使用std::shared_ptr时,要小心避免循环引用,这可能导致内存泄漏。可以使用std::weak_ptr来打破循环。

示例代码
#include <memory>

class A {
public:
    std::weak_ptr<B> b;
};

class B {
public:
    std::shared_ptr<A> a;
};

int main() {
    std::shared_ptr<A> a(new A());
    std::shared_ptr<B> b(new B());
    b->a = a; // 正常引用
    a->b = b; // 使用weak_ptr避免循环引用
    return 0;
}

3. 使用内存泄漏检测工具

在开发过程中,使用工具如Valgrind或Visual Studio的内存泄漏检测器来识别和修复内存泄漏。

4. 避免在循环中分配内存

在循环中分配和释放内存可能导致内存泄漏,尤其是在循环中忘记释放内存的情况下。尽量在循环外分配内存,或者使用容器。

示例代码
#include <vector>

class MyClass {
public:
    MyClass() { /* 构造函数 */ }
    ~MyClass() { /* 析构函数 */ }
};

int main() {
    std::vector<MyClass> myVector;
    for (int i = 0; i < 10; ++i) {
        myVector.push_back(MyClass());
    }
    // 当myVector被销毁时,所有MyClass对象的内存将自动释放
    return 0;
}

通过遵循这些策略和技巧,可以显著减少C++程序中的内存泄漏,提高代码的健壮性和效率。

案例分析与实践

内存泄漏实例解析

在C++中,内存泄漏通常发生在程序员分配了内存但忘记释放的情况下。这可能导致程序运行时占用的内存逐渐增加,最终可能导致性能下降或程序崩溃。下面通过一个具体的例子来分析内存泄漏的产生原因。

示例代码

#include <iostream>

class MemoryLeakExample {
public:
    int* data;
    MemoryLeakExample() {
        data = new int[100]; // 分配内存
        for (int i = 0; i < 100; i++) {
            data[i] = i;
        }
    }
    ~MemoryLeakExample() {
        // 忘记释放内存
    }
};

int main() {
    MemoryLeakExample example; // 创建对象
    std::cout << "Memory leak example created." << std::endl;
    return 0;
}

分析

在这个例子中,MemoryLeakExample类的构造函数分配了100个整数的内存,但析构函数中没有释放这些内存。当example对象在main函数中被销毁时,分配的内存没有被释放,导致内存泄漏。

编写无泄漏代码示例

为了避免内存泄漏,可以使用智能指针或确保在对象生命周期结束时释放内存。下面的例子展示了如何使用智能指针来管理内存,从而避免泄漏。

示例代码

#include <iostream>
#include <memory>

class NoLeakExample {
public:
    std::unique_ptr<int[]> data;
    NoLeakExample() {
        data = std::make_unique<int[]>(100); // 使用智能指针分配内存
        for (int i = 0; i < 100; i++) {
            data[i] = i;
        }
    }
    ~NoLeakExample() {
        // 智能指针会在对象销毁时自动释放内存
    }
};

int main() {
    NoLeakExample example; // 创建对象
    std::cout << "No leak example created." << std::endl;
    return 0;
}

分析

在这个改进的例子中,NoLeakExample类使用了std::unique_ptr来管理内存。std::unique_ptr是一个智能指针,它会在对象生命周期结束时自动释放所管理的内存。因此,当example对象在main函数中被销毁时,data所指向的内存会被自动释放,从而避免了内存泄漏。

使用std::shared_ptr的示例

#include <iostream>
#include <memory>

class SharedData {
public:
    int value;
    SharedData() : value(10) {
        std::cout << "SharedData object created." << std::endl;
    }
};

int main() {
    std::shared_ptr<SharedData> data1 = std::make_shared<SharedData>();
    std::shared_ptr<SharedData> data2 = data1; // 共享指针的引用计数增加

    std::cout << "data1 use count: " << data1.use_count() << std::endl;
    std::cout << "data2 use count: " << data2.use_count() << std::endl;

    // 当data1和data2都超出作用域时,引用计数为0,内存被释放
    return 0;
}

分析

在这个例子中,std::shared_ptr被用来管理SharedData对象的内存。std::shared_ptr允许多个指针共享同一块内存,通过引用计数机制来决定何时释放内存。当data1data2都超出作用域时,它们的引用计数会减少到0,此时SharedData对象的内存会被自动释放,避免了内存泄漏。

通过这些示例,我们可以看到,使用智能指针是管理C++中动态分配内存的有效方法,可以显著减少内存泄漏的风险。

总结与进阶

总结避免内存泄漏的关键点

在C++中,避免内存泄漏主要依赖于程序员的细心和对资源管理的深入理解。以下几点是关键:

  1. 使用智能指针:智能指针如std::unique_ptrstd::shared_ptr可以自动管理内存,当智能指针超出作用域时,它所管理的内存会被自动释放,从而避免内存泄漏。

  2. 避免忘记释放内存:使用new分配的内存必须用deletedelete[](对于数组)来释放。确保每次new都有对应的delete

  3. 避免重复释放内存:一旦内存被释放,再次释放会导致程序崩溃。确保内存只被释放一次。

  4. 使用RAII(Resource Acquisition Is Initialization)原则:在构造函数中获取资源,在析构函数中释放资源,确保资源的生命周期与对象的生命周期一致。

  5. 检查返回值new操作可能失败,返回nullptr。在使用new后,检查返回值是否为nullptr,避免使用未初始化的指针。

  6. 使用内存泄漏检测工具:如Valgrind和Visual Studio的内存泄漏检测器,帮助识别和定位内存泄漏。

示例:使用智能指针避免内存泄漏

#include <memory>
#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass constructed" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destructed" << std::endl;
    }
};

int main() {
    // 使用std::unique_ptr自动管理MyClass的内存
    std::unique_ptr<MyClass> myObject = std::make_unique<MyClass>();
    // myObject超出作用域后,MyClass的内存会被自动释放
    return 0;
}

在这个例子中,MyClass的实例通过std::unique_ptr管理,当main函数结束时,myObject超出作用域,MyClass的析构函数会被自动调用,内存被释放,从而避免了内存泄漏。

进阶学习资源与工具

学习资源

  • 书籍:《C++ Primer》和《Effective C++》提供了深入的C++资源管理技巧和最佳实践。
  • 在线课程:Udemy和Coursera上的C++高级课程,如“C++: From Beginner to Beyond”和“C++ Concurrency in Action”,涵盖智能指针和RAII等主题。
  • 博客和文章:如Scott Meyers的博客和Herb Sutter的文章,深入讨论C++内存管理的细节。

工具

  • Valgrind:一个强大的内存调试和分析工具,可以检测内存泄漏和使用错误。
  • Visual Studio内存泄漏检测器:Visual Studio内置的工具,用于检测和报告内存泄漏。
  • AddressSanitizer:一个快速的内存错误检测器,可以检测各种内存错误,包括使用未初始化的内存和越界访问。

示例:使用Valgrind检测内存泄漏

# 编译C++程序,开启调试信息
g++ -g myprogram.cpp -o myprogram

# 使用Valgrind运行程序,检测内存泄漏
valgrind --leak-check=full ./myprogram

在这个例子中,我们首先使用g++编译器编译C++程序,开启调试信息。然后使用Valgrind运行程序,--leak-check=full选项开启全面的内存泄漏检测,Valgrind会报告程序中所有的内存泄漏。

通过上述资源和工具的学习与实践,可以进一步提升在C++中避免和检测内存泄漏的能力,确保程序的健壮性和性能。
在这里插入图片描述

标签:std,泄漏,释放,07,23,int,09,内存,ptr
From: https://blog.csdn.net/chenjj4003/article/details/144544956

相关文章

  • 238. 除自身以外数组的乘积
    除自身以外数组的乘积给你一个整数数组nums,返回数组answer,其中answer[i]等于nums中除nums[i]之外其余各元素的乘积。题目数据保证数组nums之中任意元素的全部前缀元素和后缀的乘积都在32位整数范围内。请不要使用除法,且在O(n)时间复杂度内完成此题。示例1......
  • springboot098基于web的网上摄影工作室的开发与实现
    ......
  • springboot099大型商场应急预案管理系统
    ......
  • [HDU6807] Fake Photo
    思路考虑二分答案,那么对于每个时针分针都会生成一个合法指针区间,我们对时针和分针的限制区间分别取交集如果时针范围大于\(1h\),那么分针显然可以走到任何地方否则分针还会生成一个限制范围,我们再取交集即可实现讲真这比大模拟恶心多了,真不想写,以后拿来练马力......
  • OpenHarmony测试RS232/RS485串口方法,触觉智能SBC3528工控主板演示
    教大家介绍在OpenHarmony系统,没有串口工具的情况下如何测试RS232/RS485,使用触觉智能SBC3528工控主板演示,搭载了瑞芯微RK3568四核处理器,板载2路RS232+4路隔离RS485,集成DIDO,自研RS485自动收发驱动,支持超2KM传输距离!RS485测试方法以触觉智能SBC3528工控主板为例,如果需要测试RS485串......
  • 题解:AT_abc236_f [ABC236F] Spices
    今天2024秋令营Day1的贪心例题,来解释一下这道题贪心的思路。首先输入一个整数\(n\),表示需要处理的数字数量为\(2^n-1\),随后输入每个编号的代价,并将代价和编号存储在数组\(a\)中。接着,对代价进行排序,以便在后续处理中优先选择代价较小的数字。然后,使用一个\(vis\)数......
  • hot100-一刷-09图论(共4道题)
    题目题目链接题目描述代码实现分析:代码:题目题目链接题目描述代码实现分析:代码:题目题目链接题目描述代码实现分析:代码:题目题目链接题目描述代码实现分析:代码:......
  • Springboot校园二手交易平台 99093
    Springboot校园二手交易平台99093本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表项目功能:用户,卖家,商品分类,商品信息,在线咨询,商品需求开题报告内容一、项目背景与意义随着互联网技术的快速发展,电子......
  • 代码随想录算法训练营第三十二天|动态规划理论基础|LC509.肥波那些数|LC70.爬楼梯|LC7
    动态规划理论基础    解释:动态规划,英文:DynamicProgramming,简称DP;如果某一问题有很多重叠子问题,使用动态规划是最有效的。动态规划五部曲:    1、确定dp数组(dptable)以及下标的含义;    2、确定递推公式;    3、dp数组如何初始化;   ......
  • 江科大STM32学习:07 定时器输入捕获
    本节对应STM32F10XXX参考手册中的14.3.5输入捕获模式和14.3.6PWM输入模式1.输入捕获简介IC(InputCapture)输入捕获输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形输入捕获模式下,当通道输入引脚出......