高性能计算面经

高性能计算面经

  • C++八股文
  • 真景一面凉经
    • 自我介绍,介绍一下你做过的加速的模块(叠噪,噪声跟原图有什么关系?)
    • OpenGL和OpenCL有什么区别?
      • **1. 核心用途**
      • **2. 编程模型**
      • **3. 硬件抽象**
      • **4. API设计**
      • **5. 典型应用场景**
      • **6. 互操作性**
      • **总结表**
      • **选择依据**
    • 为什么要用OpenCL?
    • OpenGL和OpenCL同时走会有GPU抢占问题,怎么解决?为什么要内存拷贝?
    • OpenCL为什么对功耗有优化?
    • 你们是怎么做优化的(性能拆解和分析)?
    • 性能本地跑很快,部署到手机很慢怎么办?跟别的算法一起跑,性能有影响怎么办(并行,调度)?
    • OpenCL的img和buf有什么区别?
      • **1. 数据结构和存储方式**
      • **2. 访问方式**
      • **3. 性能特点**
      • **4. 典型应用场景**
      • **5. 创建方式**
      • **6. 内核代码中的访问**
      • **7. 关键区别总结**
      • **何时选择?**
      • **互操作示例**
    • 说一下内存对齐,为什么要做字节对齐
    • 你们做加速有多少人?怎么划分的?哪些模块是你独立完整实现的?
    • Neon是如何做的?
    • 稳定性和效果问题是怎么排查的?
    • 如果只能靠压测且低概率出现的问题,怎么解决?
    • 为什么想离职?期望是多少?
    • 反问:部门做服务端AI加速,有4-5个人
  • 影石一面
    • 什么是8bit量化?量化是怎么实现的?
      • **1. 什么是8bit量化?**
      • **2. 量化的核心步骤**
        • **具体实现方法**
      • **3. 量化的类型**
      • **4. 实现示例**
      • **5. 量化的优势与挑战**
      • **总结**
    • 量化之后出现精度损失怎么办?
      • 1. **校准过程优化**
      • 2. **量化策略调整**
      • 3. **模型结构调整**
      • 4. **后处理与微调**
      • 5. **工具与位宽选择**
      • 6. **高级技术融合**
      • 7. **验证与监控**
      • 实施示例(PyTorch QAT):
      • 关键点总结:
    • C和C++有什么区别?
      • 1. **编程范式**
      • 2. **核心特性**
      • 3. **内存管理**
      • 4. **标准库**
      • 5. **兼容性**
      • 6. **应用场景**
      • 7. **语法细节差异**
      • 8. **性能**
      • 总结
    • C++如何调用C函数?反过来呢?
      • **1. C++调用C函数**
        • **关键步骤**:使用 `extern "C"` 声明C函数,避免C++的名称修饰。
        • **示例**:
      • **2. C调用C++函数**
        • **关键步骤**:在C++代码中,用 `extern "C"` 导出C兼容的函数接口,并在C中声明。
        • **示例**:
      • **注意事项**
      • **总结**
    • C++中如何new和delete一个数组?delete没有加[]会怎么样?
      • **1. 如何正确分配和释放数组**
        • **分配数组**
        • **释放数组**
      • **2. 错误使用 `delete` 而非 `delete[]` 的后果**
        • **(1) 内存泄漏或资源泄漏**
        • **(2) 堆内存损坏**
        • **(3) 行为不可预测**
      • **3. 对基本类型数组的影响**
      • **4. 总结与最佳实践**
        • **建议**:
      • **示例验证**
    • C++的多态是怎么实现的?
      • **1. 虚函数表(vtable)**
      • **2. 虚函数指针(vptr)**
      • **3. 动态绑定过程**
      • **4. 关键特性与注意事项**
      • **5. 总结**
    • 类中有虚函数,析构函数应该注意什么?什么情况下会触发虚函数?
      • **一、虚函数与析构函数的注意事项**
        • **1. 虚析构函数必须存在**
        • **2. 构造函数和析构函数中避免调用虚函数**
      • **二、虚函数的触发条件**
        • **1. 通过指针或引用调用虚函数**
        • **2. 虚函数未被隐藏或覆盖**
        • **3. 虚函数未被`final`禁止重写**
      • **三、总结**
        • **关键点**
        • **示例验证**
    • 指针和引用的区别?
      • **1. 定义与本质**
        • **示例代码**
      • **2. 操作与功能**
        • **示例代码**
      • **3. 参数传递与语义**
        • **示例代码**
      • **4. 内存管理**
        • **示例代码**
      • **5. 底层实现**
      • **总结:选择指针还是引用?**
        • **核心原则**
    • 什么是深拷贝和浅拷贝?
      • **1. 浅拷贝(Shallow Copy)**
        • **示例问题**
      • **2. 深拷贝(Deep Copy)**
        • **正确使用**
      • **3. 关键区别总结**
      • **4. 何时需要深拷贝?**
      • **5. 其他语言的实现**
      • **6. 总结**
    • C++中opencv的mat是怎么深拷贝和浅拷贝的?
    • 说一下快排,二分查找
      • 快速排序(Quick Sort)
      • 二分查找(Binary Search)
      • 对比总结
    • char a = -1,在计算机内存中是怎么存储的?
      • **1. 符号性决定存储形式**
      • **2. 存储过程**
        • **情况1:有符号char(signed char)**
        • **情况2:无符号char(unsigned char)**
      • **3. 验证代码**
      • **4. 总结**
      • **5. 扩展:补码的优势**
    • C++实现sharedPtr,手写C++的string类
    • 对cache的理解,如何提高缓存命中率?
    • 数据结构中顺序存储和链式存储的优缺点
  • 大疆车载面经
    • 自我介绍+介绍简历项目
    • 围绕项目问涉及的知识点
    • 问了很多DSP的内容(项目相关)
    • 回顾你做的项目,你觉得有哪些地方可以改进?有哪些挑战?难忘的经历
    • 说说你对大疆车载的了解,反问
    • 优化手段有哪些
    • 模型推理加速是怎么整的
    • 量化部署过程
    • gpu加速有什么好处,原理是什么
    • cuda相关
    • 多线程如何使用,痛点是什么
    • 如何做同步,同步的手段有哪些?区别是什么?分别用在什么场景
    • 内存优化有哪些手段?dma相关的
    • cache cpu 内存之间的联系
  • 高性能面试问题

C++八股文

真景一面凉经

自我介绍,介绍一下你做过的加速的模块(叠噪,噪声跟原图有什么关系?)

OpenGL和OpenCL有什么区别?

OpenGL(Open Graphics Library)和OpenCL(Open Computing Language)是两种不同的开放标准,由Khronos Group维护,但它们的应用场景和设计目标有显著区别。以下是两者的主要区别:


1. 核心用途

  • OpenGL
    专注于图形渲染,用于在GPU上高效渲染2D/3D图形。它提供了一套API,允许开发者利用硬件加速绘制复杂的图形场景(如游戏、CAD、可视化等)。

  • OpenCL
    专注于通用并行计算,允许开发者利用GPU、CPU或其他计算设备(如FPGA)进行高性能计算。它适用于科学计算、物理模拟、图像处理、机器学习等需要大规模并行计算的场景。


2. 编程模型

  • OpenGL

    • 基于图形管线(Graphics Pipeline),包含顶点处理、光栅化、片段处理等固定或可编程阶段。
    • 使用着色器语言(GLSL)编写顶点着色器、片段着色器等程序。
    • 图形对象(如纹理、缓冲区、帧缓存)为中心,操作围绕绘制图形展开。
  • OpenCL

    • 基于数据并行任务并行模型,强调将计算任务分解为多个并行执行的工作项(Work Items)。
    • 使用C语言扩展(OpenCL C)编写内核(Kernel)函数,直接在计算设备上运行。
    • 数据缓冲区和计算任务为中心,操作围绕数据处理展开。

3. 硬件抽象

  • OpenGL

    • 主要面向GPU的图形功能(如光栅化、纹理映射、深度测试)。
    • 设计目标是最大化图形渲染性能,对图形硬件的细节进行抽象。
  • OpenCL

    • 面向异构计算(支持CPU、GPU、DSP等多种设备)。
    • 提供对硬件资源的更底层控制(如内存模型、工作组划分),适合优化计算密集型任务。

4. API设计

  • OpenGL

    • 状态机(State Machine)驱动,通过设置全局状态(如颜色、纹理)控制渲染流程。
    • 强调图形管线的配置(如绑定着色器、设置顶点数据)。
  • OpenCL

    • 面向任务的API,需要显式管理计算设备、上下文、命令队列等。
    • 更接近通用编程模型,支持异步任务提交和内存传输。

5. 典型应用场景

  • OpenGL

    • 游戏开发、3D建模软件(如Blender)、虚拟现实(VR)、图形界面渲染。
    • 例如:通过OpenGL绘制游戏中的角色和场景。
  • OpenCL

    • 科学计算(如流体模拟)、图像处理(如滤镜加速)、密码学、深度学习推理加速。
    • 例如:用OpenCL加速Photoshop中的图像处理算法。

6. 互操作性

  • 两者可以结合使用:
    • OpenCL处理计算任务(如物理模拟、图像生成),结果通过OpenGL渲染。
    • 通过共享内存对象(如OpenCL缓冲区与OpenGL纹理互操作),减少数据传输开销。

总结表

特性OpenGLOpenCL
目标图形渲染通用并行计算
硬件主要GPU图形单元支持多设备(GPU/CPU等)
编程模型图形管线 + 着色器数据/任务并行 + 内核函数
主要API状态机驱动(glBind*)显式任务管理(clEnqueue*)
适用领域游戏、可视化、UI科学计算、图像处理、AI

选择依据

  • 需要渲染图形(如3D模型、特效) → OpenGL 或现代替代品(Vulkan、DirectX)。
  • 需要加速数值计算(如矩阵运算、模拟) → OpenCL 或类似框架(CUDA、SYCL)。
  • 混合场景 → 结合两者(如用OpenCL生成数据,OpenGL渲染)。

为什么要用OpenCL?

OpenGL和OpenCL同时走会有GPU抢占问题,怎么解决?为什么要内存拷贝?

OpenCL为什么对功耗有优化?

你们是怎么做优化的(性能拆解和分析)?

性能本地跑很快,部署到手机很慢怎么办?跟别的算法一起跑,性能有影响怎么办(并行,调度)?

OpenCL的img和buf有什么区别?

在OpenCL中,image对象(图像内存对象)和buffer对象(缓冲区内存对象)是两种不同的内存类型,设计目标和适用场景有明显区别。以下是它们的核心差异:


1. 数据结构和存储方式

特性Buffer (cl_mem)Image (cl_mem)
数据结构一维线性内存,存储任意数据多维结构化数据(2D/3D),类似纹理
数据格式无格式要求(原始字节流)需明确指定像素格式(如CL_RGBA, CL_FLOAT等)
存储优化普通全局内存可能存储在GPU的纹理内存中(硬件缓存优化)

2. 访问方式

特性BufferImage
读写接口直接通过指针(__global float*必须通过采样器(Sampler)和坐标(如read_imagef
坐标访问线性索引(buffer[offset]多维坐标(如(x, y, z)
自动滤波不支持支持(如双线性插值、边界处理)
数据类型转换无自动转换自动转换为规范化值(如[0,1]

3. 性能特点

特性BufferImage
缓存效率依赖访问模式(适合随机访问)利用空间局部性(适合相邻像素访问)
硬件加速无特殊优化可能使用纹理硬件单元(高速缓存、滤波)
内存带宽普通全局内存带宽可能更高(纹理内存的合并访问优化)

4. 典型应用场景

场景BufferImage
适合任务通用数据(数组、结构体等)图像/纹理处理(滤波、插值等)
示例矩阵运算、物理模拟、数值计算图像卷积、光线追踪采样、体积渲染

5. 创建方式

// Buffer的创建(一维数据)
cl_mem buffer = clCreateBuffer(context, CL_MEM_READ_WRITE, size_in_bytes, NULL, &err
);// Image的创建(需指定格式和维度)
cl_image_format format;
format.image_channel_order = CL_RGBA;   // 通道顺序
format.image_channel_data_type = CL_FLOAT; // 数据类型cl_mem image = clCreateImage2D(context, CL_MEM_READ_ONLY, &format, width, height, 0,  // row_pitch(0表示连续存储)NULL, &err
);

6. 内核代码中的访问

// Buffer的访问(直接指针操作)
__kernel void buffer_kernel(__global float* buffer) {int idx = get_global_id(0);buffer[idx] = ...;
}// Image的访问(需采样器和坐标)
__constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST;__kernel void image_kernel(__read_only image2d_t input, __write_only image2d_t output) {int2 coord = (int2)(get_global_id(0), get_global_id(1));float4 pixel = read_imagef(input, sampler, coord); // 读取像素write_imagef(output, coord, pixel * 2.0f);          // 写入像素
}

7. 关键区别总结

维度BufferImage
数据布局一维线性多维结构化
硬件支持普通内存纹理内存+硬件滤波
适用场景通用计算图像/空间数据
访问灵活性高(直接指针)低(需采样器+坐标)
性能优化依赖算法空间局部性+缓存优化

何时选择?

  • 用Buffer:
    需要处理任意结构的原始数据(如数值数组、自定义结构体)或需要直接内存操作(如原子操作)。

  • 用Image:
    处理图像/纹理数据,且需要利用硬件加速的滤波(如双线性插值)、规范化坐标(如[0,1]范围)或空间局部性优化(如卷积核计算)。


互操作示例

两者可以结合使用:

  • 用Buffer存储中间计算结果,再转换为Image进行渲染。
  • 从Image读取数据后,用Buffer进行非图像类计算(如统计像素值)。

说一下内存对齐,为什么要做字节对齐

你们做加速有多少人?怎么划分的?哪些模块是你独立完整实现的?

Neon是如何做的?

稳定性和效果问题是怎么排查的?

如果只能靠压测且低概率出现的问题,怎么解决?

为什么想离职?期望是多少?

反问:部门做服务端AI加速,有4-5个人

影石一面

网上的参考:

什么是8bit量化?量化是怎么实现的?

8bit量化是一种通过降低数据精度来减少存储和计算开销的技术,常用于优化深度学习模型。以下是详细的解释和实现步骤:

1. 什么是8bit量化?

  • 核心概念:将高精度(如32位浮点数)数据映射到8位整数(范围通常为0-255或无符号整数,或-128-127有符号整数),以减少内存占用和加速计算。
  • 应用场景:主要用于模型推理阶段的优化,适用于移动端、嵌入式设备等资源受限环境。

2. 量化的核心步骤

量化通过以下公式实现:
q = round ( x S + Z ) 和 x ′ = S ⋅ ( q − Z ) q = \text{round}\left(\frac{x}{S} + Z\right) \quad \text{和} \quad x' = S \cdot (q - Z) q=round(Sx+Z)x=S(qZ)
其中:

  • (x):原始浮点数值。
  • (q):量化后的8位整数。
  • (S)(缩放因子):浮点范围与整数范围的比值。
  • (Z)(零点):将浮点零点对齐到整数的偏移量。
具体实现方法
  1. 确定浮点范围

    • 非对称量化:统计张量中的最小值( x min x_{\text{min}} xmin)和最大值( x max x_{\text{max}} xmax)。
    • 对称量化:取绝对值最大的值( x max = max ⁡ ( ∣ x min ∣ , ∣ x max ∣ ) x_{\text{max}} = \max(|x_{\text{min}}|, |x_{\text{max}}|) xmax=max(xmin,xmax)),范围变为 [ − x max , x max ] [-x_{\text{max}}, x_{\text{max}}] [xmax,xmax]
  2. 计算缩放因子((S))

    • 非对称量化
      S = x max − x min 2 8 − 1 ( 如用无符号8位整数 ) S = \frac{x_{\text{max}} - x_{\text{min}}}{2^8 - 1} \quad (\text{如用无符号8位整数}) S=281xmaxxmin(如用无符号8位整数)
    • 对称量化
      S = x max 2 7 − 1 ( 如用有符号8位整数,范围-128 127 ) S = \frac{x_{\text{max}}}{2^{7} - 1} \quad (\text{如用有符号8位整数,范围-128~127}) S=271xmax(如用有符号8位整数,范围-128 127)
  3. 计算零点((Z))(仅非对称量化需要):
    Z = round ( 0 − x min S ) Z = \text{round}\left(0 - \frac{x_{\text{min}}}{S}\right) Z=round(0Sxmin)

    • 用于对齐浮点数0与整数零点(例如,处理负数)。
  4. 量化与反量化

    • 量化:将浮点数转换为整数:
      q = clip ( round ( x S + Z ) , 0 , 255 ) q = \text{clip}\left(\text{round}\left(\frac{x}{S} + Z\right), 0, 255\right) q=clip(round(Sx+Z),0,255)
    • 反量化:恢复近似浮点值:
      x ′ = S ⋅ ( q − Z ) x' = S \cdot (q - Z) x=S(qZ)

3. 量化的类型

  • 训练后量化(Post-Training Quantization, PTQ)
    • 直接对训练好的模型进行校准(统计各层输入/输出的范围),无需重新训练。
    • 速度快但可能损失精度。
  • 量化感知训练(Quantization-Aware Training, QAT)
    • 在训练时模拟量化过程,让模型适应低精度误差。
    • 精度更高但需要额外训练时间。

4. 实现示例

以非对称量化为例:

  • 输入浮点范围:[-3.0, 5.0]
  • 计算缩放因子
    S = 5.0 − ( − 3.0 ) 255 ≈ 0.03137 S = \frac{5.0 - (-3.0)}{255} \approx 0.03137 S=2555.0(3.0)0.03137
  • 计算零点
    Z = round ( 0 − − 3.0 0.03137 ) ≈ 96 Z = \text{round}\left(0 - \frac{-3.0}{0.03137}\right) \approx 96 Z=round(00.031373.0)96
  • 量化值
    q = round ( − 3.0 0.03137 + 96 ) = 0 ( 下限 ) q = \text{round}\left(\frac{-3.0}{0.03137} + 96\right) = 0 \quad (\text{下限}) q=round(0.031373.0+96)=0(下限)
    q = round ( 5.0 0.03137 + 96 ) = 255 ( 上限 ) q = \text{round}\left(\frac{5.0}{0.03137} + 96\right) = 255 \quad (\text{上限}) q=round(0.031375.0+96)=255(上限)

5. 量化的优势与挑战

  • 优势
    • 模型大小减少约75%(32位→8位)。
    • 内存带宽需求降低,推理速度提升(支持整数运算硬件)。
  • 挑战
    • 精度损失需通过校准或QAT缓解。
    • 某些层(如残差连接)对量化敏感,需特殊处理。

总结

8bit量化通过缩放因子和零点将浮点数映射到8位整数,显著压缩模型并加速推理。其实现需权衡精度与效率,常用方法包括训练后量化和量化感知训练。实际应用中,TensorFlow Lite、PyTorch等框架已提供自动化工具简化流程。

量化之后出现精度损失怎么办?

模型量化后出现精度损失是常见问题,但可通过多种方法缓解。以下是分步解决方案:

1. 校准过程优化

  • 使用代表性数据:确保校准数据集覆盖真实场景的数据分布,避免偏差。
  • 动态范围调整:采用百分位数校准(如99.99%)而非最大值,避免异常值影响范围估计。
  • 逐通道量化(Per-Channel):对卷积层按通道单独量化,减少权重分布差异带来的误差。

2. 量化策略调整

  • 混合精度量化:对敏感层(如首尾层、注意力机制)保留FP16,其他层量化到INT8。
  • 对称与非对称量化:尝试激活层用非对称量化(处理ReLU后的非负值),权重用对称量化。
  • 量化感知训练(QAT):在训练中模拟量化噪声,微调模型参数以适应低精度。

3. 模型结构调整

  • 插入归一化层:在激活函数前添加BatchNorm或LayerNorm,稳定激活值分布。
  • 避免敏感操作:减少通道间差异大的结构(如深度可分离卷积),或用量化友好型结构(如MobileNet)。

4. 后处理与微调

  • 量化后微调:使用少量数据对量化模型微调,调整参数补偿量化误差。
  • 逐层误差分析:对比量化前后各层输出,针对高误差层进行优化(如提升其量化位宽)。

5. 工具与位宽选择

  • 更换量化工具:尝试TensorRT、TFLite等不同框架,利用其优化特性。
  • 调整位宽:对关键层尝试INT16,或在支持的情况下使用混合位宽(如8/4位混合)。

6. 高级技术融合

  • 知识蒸馏:用原模型指导量化模型训练,提升小模型精度。
  • 自适应量化:动态调整不同输入的量化参数,平衡精度与速度。

7. 验证与监控

  • 分阶段量化:逐步量化模型部分层,监控精度变化,定位敏感模块。
  • 鲁棒性测试:在边缘案例(Corner Cases)上验证量化模型表现,针对性增强。

实施示例(PyTorch QAT):

import torch
from torch.quantization import QuantStub, DeQuantStub, prepare_qat, convertclass QATModel(torch.nn.Module):def __init__(self, original_model):super().__init__()self.quant = QuantStub()self.model = original_modelself.dequant = DeQuantStub()def forward(self, x):x = self.quant(x)x = self.model(x)x = self.dequant(x)return x# 加载预训练模型
model = ... # 原始模型
qat_model = QATModel(model)# 配置量化参数
qat_model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')# 准备并训练
prepare_qat(qat_model, inplace=True)
# 微调训练循环(模拟量化噪声)
train(qat_model, train_loader, epochs=5)# 转换为量化模型
quantized_model = convert(qat_model.eval())

关键点总结:

  • 校准数据质量量化策略是影响精度的核心因素。
  • QAT通常比后训练量化(PTQ)效果更好,但需训练资源。
  • 模型本身对量化的友好性(如结构设计)决定上限,必要时需结构调整。

通过上述方法逐步调试,通常可将精度损失控制在1%以内,甚至无损量化。若仍不达标,需权衡性能与精度,考虑硬件支持的极限位宽或异构计算方案。

C和C++有什么区别?

C和C++是两种广泛使用的编程语言,虽然C++最初是作为C的扩展开发的,但两者在设计和用途上有显著区别。以下是它们的主要差异:


1. 编程范式

  • C过程式编程语言,强调函数和步骤的分解,通过函数模块化解决问题。
  • C++多范式语言,支持面向对象编程(类、继承、多态)、泛型编程(模板)、过程式编程,甚至函数式编程。

2. 核心特性

  • C

    • 无类和对象的概念。
    • 不支持函数重载(同名函数)和运算符重载。
    • 没有引用(仅用指针),无异常处理机制。
    • 结构体(struct)不能包含函数。
  • C++

    • 面向对象特性:类(class)、继承、多态(虚函数)、封装。
    • 函数重载:允许同名函数根据参数不同实现不同功能。
    • 运算符重载:例如自定义+操作符的行为。
    • 引用类型:通过int& ref = a直接操作变量。
    • 模板:支持泛型编程(如vector<T>)。
    • 异常处理try/catch/throw机制。
    • 命名空间:避免命名冲突(namespace)。

3. 内存管理

  • C:手动管理内存,仅通过malloc/callocfree操作堆内存。
  • C++:引入newdelete运算符,支持构造/析构函数自动管理资源(如RAII技术)。

4. 标准库

  • C:标准库较小,提供基础功能(如stdio.h, stdlib.h, 字符串处理等)。
  • C++:包含C的大部分库,并扩展了面向对象和泛型的库:
    • STL(标准模板库):容器(vector, map)、算法(sort, find)、迭代器。
    • 输入输出流cout/cin代替printf/scanf

5. 兼容性

  • C++兼容C:大多数C代码可在C++中编译(需注意少数语法差异,如强制类型转换)。
  • C不兼容C++:C无法直接使用C++的类、引用等特性。

6. 应用场景

  • C:适合底层开发(操作系统、嵌入式系统、驱动程序),追求极致性能和硬件控制。
  • C++:适合大型软件、游戏引擎、GUI应用、高频交易等,兼顾性能与抽象需求。

7. 语法细节差异

特性CC++
空指针NULLnullptr(更安全)
默认返回值函数不写返回值默认int必须显式声明返回值类型
结构体声明定义变量需加struct关键字可直接使用类型名(如MyStruct s;
常量定义const变量可能不是真正的常量const为真正常量,支持常量表达式

8. 性能

  • 两者性能接近,但C++的某些高级特性(如虚函数、RTTI)可能带来微小开销,可通过谨慎编码规避。
  • C更贴近硬件,适合对性能极度敏感的场景。

总结

  • C:简单、高效,适合底层和资源受限环境。
  • C++:功能丰富,适合复杂项目,通过抽象提高代码可维护性,同时保持高性能。

选择时需根据项目需求:若需要面向对象或高级抽象,选C++;若追求极简和直接硬件控制,选C。许多开发者会同时掌握两者以应对不同场景。

C++如何调用C函数?反过来呢?

在C++和C之间相互调用函数时,由于两种语言的编译机制和符号命名规则不同,需要特别注意名称修饰(Name Mangling)链接规范(Linkage Specification)。以下是具体实现方法:


1. C++调用C函数

关键步骤:使用 extern "C" 声明C函数,避免C++的名称修饰。
示例
  1. C代码(example.c

    #include <stdio.h>void c_function() {printf("This is a C function.\n");
    }
    
  2. C头文件(example.h:声明时添加 extern "C"(仅在C++中生效):

    #ifdef __cplusplus
    extern "C" {  // 告诉C++编译器:以下函数按C的规则编译和链接
    #endifvoid c_function();#ifdef __cplusplus
    }
    #endif
    
  3. C++代码(main.cpp

    #include "example.h"int main() {c_function();  // 正确调用C函数return 0;
    }
    
  4. 编译命令

    gcc -c example.c -o example.o          # 编译C代码
    g++ main.cpp example.o -o main         # 编译C++并链接C的目标文件
    

2. C调用C++函数

关键步骤:在C++代码中,用 extern "C" 导出C兼容的函数接口,并在C中声明。
示例
  1. C++代码(cpp_code.cpp

    #include <iostream>// 导出C兼容的函数(禁止名称修饰)
    extern "C" void cpp_function() {std::cout << "This is a C++ function called from C.\n";
    }
    
  2. C头文件(cpp_header.h

    #ifdef __cplusplus
    extern "C" {  // C++需要extern "C",C编译器会忽略这段代码
    #endifvoid cpp_function();  // 在C中声明为普通函数#ifdef __cplusplus
    }
    #endif
    
  3. C代码(main.c

    #include "cpp_header.h"int main() {cpp_function();  // 调用C++函数return 0;
    }
    
  4. 编译命令

    g++ -c cpp_code.cpp -o cpp_code.o       # 编译C++代码
    gcc main.c cpp_code.o -o main -lstdc++  # 编译C并链接C++的目标文件(需C++标准库)
    

注意事项

  1. 类型兼容性

    • 被调用的函数参数和返回值必须是C兼容的类型(如基本类型、指针、struct等)。
    • 避免传递C++特有类型(如类、模板、引用等)。
  2. 全局函数限制

    • C无法直接调用C++的类成员函数或操作对象,需通过封装全局函数间接实现:
      extern "C" void wrapper_for_cpp_class_method() {MyClass obj;obj.method();
      }
      
  3. 名称修饰问题

    • C++函数若未用 extern "C",其符号名会被编译器修饰(如 _Z12func_namev),导致C代码无法找到该函数。
  4. 编译器和链接器

    • 确保C和C++代码分别用对应编译器编译(如 gccg++)。
    • 链接时可能需要指定C++标准库(如 -lstdc++)。

总结

场景关键方法示例
C++调用C函数在C头文件中用 extern "C" 包裹声明extern "C" { void c_func(); }
C调用C++函数在C++代码中用 extern "C" 导出函数extern "C" void cpp_func() { ... }

通过合理使用 extern "C" 和类型兼容性约束,可以无缝实现C与C++的互操作。

C++中如何new和delete一个数组?delete没有加[]会怎么样?

在C++中,动态分配和释放数组需要严格遵循new[]delete[]的配对使用规则。以下是具体说明及错误使用的后果:


1. 如何正确分配和释放数组

分配数组

使用 new[] 运算符为数组动态分配内存,并初始化元素(若为类类型,调用构造函数):

// 分配一个包含10个int的数组
int* intArray = new int[10]; // 分配一个包含5个对象的数组(假设MyClass有默认构造函数)
MyClass* objArray = new MyClass[5]; 
释放数组

必须使用 delete[] 释放数组内存(若为类类型,调用每个元素的析构函数):

delete[] intArray;  // 正确释放int数组
delete[] objArray;  // 正确释放对象数组

2. 错误使用 delete 而非 delete[] 的后果

如果误用 delete 释放数组,会导致未定义行为(Undefined Behavior, UB),具体表现可能包括:

(1) 内存泄漏或资源泄漏
  • 类类型数组delete 只会调用第一个元素的析构函数,其余元素的析构函数不会被调用。
    若类持有动态资源(如堆内存、文件句柄),这些资源将泄漏。
    class ResourceHolder {
    public:ResourceHolder() { data = new int[100]; }  // 分配资源~ResourceHolder() { delete[] data; }       // 应在析构函数释放资源
    private:int* data;
    };ResourceHolder* array = new ResourceHolder[3];
    delete array;  // 错误!只有第一个元素调用析构函数,剩余2个资源泄漏!
    
(2) 堆内存损坏
  • new[]delete[] 的实现通常会在分配的内存块头部记录数组长度
    若用 delete 释放数组,内存管理器可能无法正确识别分配块的大小,导致堆结构破坏,引发程序崩溃或难以调试的错误。
(3) 行为不可预测
  • 未定义行为的具体表现取决于编译器和运行时环境,可能包括:
    • 程序直接崩溃。
    • 内存泄漏但程序看似正常运行。
    • 后续内存操作出现诡异错误。

3. 对基本类型数组的影响

即使数组元素是基本类型(如intfloat),仍需使用 delete[]

int* arr = new int[10];
delete arr;  // 错误!应为 delete[] arr;
  • 虽然基本类型没有析构函数,但 new[] 可能仍会记录数组长度信息。
    使用 delete 会导致内存管理器错误解析分配块,可能引发堆损坏。

4. 总结与最佳实践

操作正确用法错误用法后果
分配数组new T[n]--
释放数组delete[] ptrdelete ptr未定义行为(内存泄漏、崩溃等)
分配单个对象new T--
释放单个对象delete ptrdelete[] ptr未定义行为(堆损坏)
建议
  • 避免手动管理数组:优先使用 std::vectorstd::unique_ptr<T[]> 等容器/智能指针。
    // 使用智能指针自动管理数组
    std::unique_ptr<int[]> safeArray(new int[10]);
    
  • 严格配对:若必须手动管理,确保 new[]delete[] 严格成对出现。

示例验证

#include <iostream>class Test {
public:Test() { std::cout << "Constructor\n"; }~Test() { std::cout << "Destructor\n"; }
};int main() {Test* arr = new Test[3];delete[] arr;  // 正确:输出3次"Destructor"// delete arr; // 错误:仅输出1次"Destructor",其余泄漏return 0;
}

输出(正确使用 delete[]

Constructor
Constructor
Constructor
Destructor
Destructor
Destructor

输出(错误使用 delete

Constructor
Constructor
Constructor
Destructor  // 只有第一个元素被销毁

遵循规则可避免资源管理错误,确保程序健壮性。

C++的多态是怎么实现的?

C++的多态性主要通过虚函数(Virtual Functions)和虚函数表(vtable)机制来实现,其核心在于动态绑定(Dynamic Binding),允许在运行时确定调用的具体函数。以下是详细的实现机制:


1. 虚函数表(vtable)

  • 定义:每个包含虚函数的类(或从包含虚函数的类派生的类)都有一个虚函数表。这是一个隐式的静态数组,存储该类所有虚函数的地址。
  • 结构
    • 表中条目按虚函数的声明顺序排列。
    • 若子类重写虚函数,表中对应条目替换为子类函数的地址;未重写则保留父类函数地址。
  • 多重继承:若类继承多个含虚函数的基类,会为每个基类维护一个独立的虚函数表。

2. 虚函数指针(vptr)

  • 隐藏成员:每个对象实例内部包含一个指向其所属类虚函数表的指针(vptr),通常位于对象内存布局的起始位置。
  • 初始化
    • 在对象构造时,由构造函数隐式初始化vptr,使其指向当前类的虚函数表。
    • 构造顺序:基类→派生类,故vptr在构造过程中可能被多次修改,最终指向实际类型的虚函数表。
  • 析构过程:析构时反向调整vptr,确保销毁阶段仍能正确调用当前类的虚函数。

3. 动态绑定过程

  • 调用步骤
    1. 通过对象指针/引用找到vptr
    2. 通过vptr定位到虚函数表。
    3. 根据函数在表中的偏移量获取实际函数地址。
    4. 执行该地址对应的函数。
  • 示例
    class Animal {
    public:virtual void speak() { cout << "Animal sound" << endl; }
    };class Dog : public Animal {
    public:void speak() override { cout << "Woof!" << endl; }
    };Animal* animal = new Dog();
    animal->speak(); // 输出 "Woof!"
    
    • animal->speak()运行时,通过Dog对象的vptr找到Dog的虚函数表,调用Dog::speak()

4. 关键特性与注意事项

  • 虚析构函数:若通过基类指针删除派生类对象,基类析构函数必须为虚函数,以确保正确调用派生类析构函数,避免资源泄漏。
  • 性能开销
    • 空间:每个对象需存储vptr(通常4/8字节)。
    • 时间:虚函数调用需两次间接寻址(访问vptr→查表→跳转),略慢于非虚函数。
  • 覆盖控制
    • override(C++11):显式标记重写,编译器检查签名一致性。
    • final:禁止进一步重写或继承。

5. 总结

C++多态通过虚函数表虚函数指针实现动态绑定:

  1. 编译时:生成虚函数表,记录虚函数地址。
  2. 运行时:通过对象的vptr查找虚函数表,确定实际调用的函数。

这种机制在保持高效性的同时,提供了灵活的运行时多态支持,是C++面向对象设计的核心基石之一。

类中有虚函数,析构函数应该注意什么?什么情况下会触发虚函数?

在C++中,当类包含虚函数时,析构函数的设计虚函数的触发条件是保证多态行为正确性的关键。以下是详细解答:


一、虚函数与析构函数的注意事项

1. 虚析构函数必须存在
  • 问题背景
    若通过基类指针删除一个派生类对象,而基类析构函数未声明为虚函数,会导致派生类的析构函数未被调用,引发资源泄漏(如内存、文件句柄未释放)。

  • 正确做法

    class Base {
    public:virtual ~Base() {}  // 虚析构函数virtual void foo() = 0;
    };class Derived : public Base {
    public:~Derived() { /* 释放派生类资源 */ }void foo() override { /* 实现 */ }
    };Base* obj = new Derived();
    delete obj;  // 正确调用 Derived::~Derived() 和 Base::~Base()
    
    • 基类析构函数必须为虚函数,确保通过基类指针删除对象时,派生类析构逻辑被执行。
2. 构造函数和析构函数中避免调用虚函数
  • 问题
    在构造函数和析构函数中调用虚函数时,动态绑定失效,实际调用的是当前类的版本(而非派生类重写的版本)。

    • 构造函数执行时,派生类尚未初始化,虚函数表(vtable)指向当前类的虚函数。
    • 析构函数执行时,派生类已部分销毁,虚函数表可能已恢复为基类版本。
  • 示例

    class Base {
    public:Base() { callFoo(); }  // 危险:调用 Base::foo()virtual void foo() { cout << "Base::foo" << endl; }void callFoo() { foo(); }
    };class Derived : public Base {
    public:void foo() override { cout << "Derived::foo" << endl; }
    };Derived d; // 输出 "Base::foo",而非 "Derived::foo"
    

二、虚函数的触发条件

虚函数的动态绑定(多态)仅在以下场景生效:

1. 通过指针或引用调用虚函数
  • 动态绑定触发条件
    Base* ptr = new Derived();
    ptr->foo();      // 调用 Derived::foo()Base& ref = *ptr;
    ref.foo();       // 调用 Derived::foo()
    
    • 若直接通过对象实例调用虚函数,静态绑定生效,无多态行为:
      Derived d;
      d.foo();        // 调用 Derived::foo()
      Base b = d;
      b.foo();        // 调用 Base::foo()(对象切片,无多态)
      
2. 虚函数未被隐藏或覆盖
  • 函数签名必须一致
    派生类的虚函数需与基类虚函数名称、参数、返回类型完全一致(协变返回类型除外)。
    • 使用 override 关键字(C++11)可让编译器检查重写是否合法:
      class Derived : public Base {
      public:void foo() override { /* ... */ }  // 显式标记重写
      };
      
3. 虚函数未被final禁止重写
  • 若基类虚函数被声明为 final,派生类无法重写:
    class Base {
    public:virtual void foo() final {}  // 禁止派生类重写
    };
    

三、总结

关键点
  1. 虚析构函数

    • 基类的析构函数必须为虚函数,确保多态删除对象时资源正确释放。
    • 若类可能被继承,且需通过基类指针操作对象,析构函数应声明为虚函数。
  2. 虚函数触发条件

    • 通过基类指针或引用调用虚函数时触发动态绑定。
    • 构造函数和析构函数中调用虚函数可能导致非预期行为。
  3. 设计建议

    • 若类含虚函数,析构函数应始终为虚函数(除非明确禁止继承)。
    • 使用 overridefinal 增强代码可读性和安全性。
示例验证
#include <iostream>
using namespace std;class Animal {
public:virtual ~Animal() { cout << "~Animal()" << endl; }virtual void speak() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:~Dog() { cout << "~Dog()" << endl; }void speak() override { cout << "Woof!" << endl; }
};int main() {Animal* animal = new Dog();animal->speak();  // 输出 "Woof!"delete animal;    // 正确调用 ~Dog() 和 ~Animal()return 0;
}

输出

Woof!
~Dog()
~Animal()

通过合理使用虚析构函数和虚函数,可以确保C++多态行为的正确性和资源管理的安全性。

指针和引用的区别?

在C++中,指针(Pointer)和引用(Reference)都是用于间接访问数据的工具,但它们在语义、用法和底层实现上存在显著差异。以下是它们的核心区别及适用场景:


1. 定义与本质

特性指针引用
本质存储变量地址的变量(独立实体)变量的别名(与原变量共享内存地址)
初始化要求可以不初始化(但建议初始化为nullptr必须初始化,且绑定后不可更改指向对象
空值(Null)可以指向空地址(nullptr不能为空,必须绑定有效对象
示例代码
int a = 10;// 指针
int* p = &a;    // 合法
int* p2;        // 合法(但未初始化,危险!)
p2 = nullptr;   // 合法// 引用
int& r = a;     // 合法
int& r2;        // 编译错误!引用必须初始化

2. 操作与功能

特性指针引用
重新赋值可修改指向其他对象一旦绑定,无法更改(始终指向初始对象)
内存操作支持指针算术(如p++p += 1不支持算术运算(仅是别名)
多级间接访问支持多级指针(如int** pp不支持多级引用(但可通过typedef间接实现)
解引用方式需显式使用*(如*p = 20直接使用原变量名(如r = 20
示例代码
int arr[3] = {1, 2, 3};// 指针算术
int* p = arr;
p++;        // 指向arr[1]
*p = 20;    // arr[1] = 20// 引用无法重新绑定
int x = 5, y = 10;
int& ref = x;
ref = y;    // 实际是x = y(ref仍绑定x,而非y)

3. 参数传递与语义

场景指针引用
函数参数显式传递地址,需检查空值隐式传递别名,语法更简洁,无需空值检查
函数重载void func(int*)void func(int&)视为不同函数
返回值可以返回指针(如动态分配的对象)可以返回引用(如避免对象拷贝)
示例代码
// 指针参数
void swap_ptr(int* a, int* b) {if (a && b) { // 需检查空指针int tmp = *a;*a = *b;*b = tmp;}
}// 引用参数(更安全)
void swap_ref(int& a, int& b) {int tmp = a;a = b;b = tmp;
}

4. 内存管理

特性指针引用
动态内存可指向堆内存(需手动new/delete通常绑定栈内存,生命周期由作用域管理
资源所有权可能涉及资源所有权转移(需谨慎管理)不拥有资源,仅作为别名
示例代码
// 指针管理动态内存
int* p = new int(10);
delete p;  // 必须手动释放// 引用绑定栈对象
int x = 10;
int& r = x;  // 无需释放

5. 底层实现

  • 指针:直接对应内存地址,操作透明(如mov指令操作地址值)。
  • 引用:通常由编译器实现为“自动解引用的指针”,但在语法层面隐藏了地址操作。

总结:选择指针还是引用?

场景推荐选择原因
可选参数(允许空值)指针(如nullptr表示无数据)引用无法表示空值
函数内需重新绑定对象指针引用一旦绑定不可修改
避免拷贝大对象引用传递别名而非副本,高效
实现多态指针或引用均可(虚函数通过引用/指针触发多态)
资源管理(如动态内存)指针(配合RAII或智能指针)引用不拥有资源
核心原则
  • 优先使用引用:在参数传递、返回值优化等场景中,引用更安全、简洁。
  • 必须使用指针:当需要处理动态内存、可选参数或多级间接访问时。

什么是深拷贝和浅拷贝?

在编程中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种不同的对象拷贝方式,主要区别在于对资源所有权内存管理的处理方式。以下是它们的核心区别及实际应用场景:


1. 浅拷贝(Shallow Copy)

  • 定义
    仅复制对象的成员值(包括指针的值),而不复制指针指向的实际数据

    • 拷贝后的对象与原对象的指针成员指向同一块内存。
  • 特点

    • 速度快(仅复制指针地址,不涉及内存分配和数据复制)。
    • 潜在风险:多个对象共享同一资源,可能导致悬空指针双重释放等问题。
  • C++中的默认行为

    class ShallowExample {int* data;
    public:// 默认拷贝构造函数和赋值运算符执行浅拷贝ShallowExample(const ShallowExample& other) : data(other.data) {}
    };
    
示例问题
ShallowExample obj1;
ShallowExample obj2 = obj1; // 浅拷贝
// obj1和obj2的data指向同一内存
delete obj1.data;           // 释放内存
obj2.data[0] = 10;         // 危险!obj2.data已是悬空指针

2. 深拷贝(Deep Copy)

  • 定义
    不仅复制对象的成员值,还会为指针成员分配新内存,并复制原指针指向的所有数据。

    • 拷贝后的对象与原对象完全独立,资源互不影响。
  • 特点

    • 安全性高(资源独立,避免共享冲突)。
    • 速度较慢(需要分配内存并复制数据)。
  • 实现方式

    class DeepExample {int* data;size_t size;
    public:// 自定义深拷贝构造函数DeepExample(const DeepExample& other) {size = other.size;data = new int[size];        // 分配新内存memcpy(data, other.data, size * sizeof(int)); // 复制数据}// 深拷贝赋值运算符DeepExample& operator=(const DeepExample& other) {if (this != &other) {       // 防止自赋值delete[] data;          // 释放旧资源size = other.size;data = new int[size];   // 分配新内存memcpy(data, other.data, size * sizeof(int));}return *this;}~DeepExample() { delete[] data; }
    };
    
正确使用
DeepExample obj1;
DeepExample obj2 = obj1; // 深拷贝:obj2.data是独立内存块
delete obj1.data;        // 不影响obj2.data
obj2.data[0] = 10;       // 安全

3. 关键区别总结

特性浅拷贝深拷贝
资源所有权共享资源(多个对象指向同一内存)独立资源(每个对象拥有自己的内存副本)
内存操作不分配新内存,仅复制指针值分配新内存并复制数据
性能慢(取决于数据大小)
安全性低(需手动管理共享资源)高(资源隔离,避免冲突)
适用场景只读共享数据、性能敏感且无资源所有权的场景需独立管理资源的场景(如动态数组、字符串等)

4. 何时需要深拷贝?

  • 对象管理资源时
    若类包含指针成员,且该指针指向动态分配的内存(如数组、文件句柄、网络连接等),必须实现深拷贝。
  • 遵循“三法则”
    在C++中,如果类需要自定义析构函数,通常也需要自定义拷贝构造函数拷贝赋值运算符(即深拷贝逻辑)。

5. 其他语言的实现

  • Java/Python
    • 默认的赋值和拷贝(如clone()copy.copy())通常是浅拷贝。
    • 深拷贝需手动实现(如Java实现Cloneable接口,Python使用copy.deepcopy())。
  • JavaScript
    • 对象展开符({...obj})或Object.assign()是浅拷贝。
    • 深拷贝需递归复制或使用JSON.parse(JSON.stringify(obj))(局限性:无法处理函数、循环引用)。

6. 总结

  • 浅拷贝:适合轻量级、无需资源隔离的场景,但需谨慎管理共享资源。
  • 深拷贝:确保资源独立性,是管理动态内存或独占资源的必要手段。
  • 在C++中:深拷贝需手动实现;在高级语言中(如Python),可依赖内置方法,但仍需理解底层逻辑以避免陷阱。

C++中opencv的mat是怎么深拷贝和浅拷贝的?

说一下快排,二分查找

快速排序(Quick Sort)

算法思想
快速排序采用分治策略,通过选择一个基准元素将数组分成两部分,使左边元素均小于基准,右边元素均大于基准,然后递归地对子数组排序。

步骤详解

  1. 选择基准(Pivot)
    通常可选第一个元素、最后一个元素、中间元素或随机元素作为基准。优化方法如三数取中法(选首、中、尾的中位数)可减少最坏情况概率。

  2. 分区(Partition)
    重新排列数组,使小于基准的元素位于左侧,大于基准的位于右侧。最终基准元素的位置即为分区点。

    • 双指针法
      i = 左边界 - 1
      pivot = 右边界元素
      for j from 左边界 to 右边界-1:if arr[j] <= pivot:i += 1swap arr[i] 和 arr[j]
      swap arr[i+1] 和 arr[右边界]
      return i+1
      
  3. 递归排序
    对分区后的左右子数组重复上述步骤,直到子数组长度为1。

时间复杂度

  • 平均:(O(n \log n))
  • 最坏(已排序数组+固定基准):(O(n^2))
  • 优化后(随机基准):接近平均情况

代码示例

#include <iostream>
#include <vector>
using namespace std;// 分区函数:将数组分为左右两部分,返回基准元素的正确索引
int partition(vector<int>& arr, int low, int high) {int pivot = arr[high];  // 选择最后一个元素作为基准int i = low - 1;        // i标记比基准小的元素的右边界// 遍历数组,将小于等于基准的元素交换到左侧for (int j = low; j < high; j++) {if (arr[j] <= pivot) {i++;swap(arr[i], arr[j]);}}// 将基准元素放到正确的位置(i+1)swap(arr[i + 1], arr[high]);return i + 1;
}// 快速排序递归函数
void quickSort(vector<int>& arr, int low, int high) {if (low < high) {int pi = partition(arr, low, high);  // 获取基准位置quickSort(arr, low, pi - 1);         // 递归排序左半部分quickSort(arr, pi + 1, high);        // 递归排序右半部分}
}// 测试快速排序
void testQuickSort() {vector<int> arr = {10, 7, 8, 9, 1, 5};cout << "原始数组: ";for (int num : arr) cout << num << " ";cout << endl;quickSort(arr, 0, arr.size() - 1);cout << "排序后数组: ";for (int num : arr) cout << num << " ";cout << endl;
}

二分查找(Binary Search)

算法思想
在有序数组中,通过不断将搜索区间对半分,快速定位目标值的位置。

步骤详解

  1. 初始化边界
    左边界 left = 0,右边界 right = len(arr) - 1

  2. 循环查找

    • 计算中间索引 mid = left + (right - left) // 2(避免整数溢出)。
    • arr[mid] == target,返回 mid
    • arr[mid] < target,调整左边界 left = mid + 1
    • 否则调整右边界 right = mid - 1
  3. 终止条件
    left > right 时,表示目标不存在,返回 -1。

时间复杂度

  • (O(\log n)),每次将搜索范围缩小一半。

代码示例

#include <iostream>
#include <vector>
using namespace std;// 二分查找函数(返回目标索引,未找到返回-1)
int binarySearch(const vector<int>& arr, int target) {int left = 0;int right = arr.size() - 1;while (left <= right) {int mid = left + (right - left) / 2;  // 避免整数溢出if (arr[mid] == target) {return mid;} else if (arr[mid] < target) {left = mid + 1;  // 目标在右半部分} else {right = mid - 1; // 目标在左半部分}}return -1;
}// 测试二分查找
void testBinarySearch() {vector<int> arr = {1, 3, 5, 7, 9};int target = 5;cout << "有序数组: ";for (int num : arr) cout << num << " ";cout << endl;int index = binarySearch(arr, target);if (index != -1) {cout << "目标 " << target << " 的索引为: " << index << endl;} else {cout << "目标 " << target << " 未找到" << endl;}
}

对比总结

算法应用场景时间复杂度核心操作关键点
快速排序大规模数据排序平均 (O(n \log n))分区与递归基准选择、原地排序
二分查找有序数据查询(O(\log n))折半比较边界处理、有序前提

注意事项

  • 快速排序需注意基准选择优化以避免最坏情况。
  • 二分查找必须确保输入数组有序,且正确处理中间值计算防止溢出。

char a = -1,在计算机内存中是怎么存储的?

在C/C++中,char a = -1; 在内存中的存储方式如下:

1. 符号性决定存储形式

  • char的符号性
    char的默认符号性由编译器决定:

    • 有符号(signed char):范围是 -128127(8位补码)。
    • 无符号(unsigned char):范围是 0255(8位纯二进制)。

    大多数编译器(如GCC/x86)默认 char有符号,但某些平台(如ARM)可能默认为无符号。
    需通过编译选项或显式声明(signed char/unsigned char)明确符号性。


2. 存储过程

情况1:有符号char(signed char)
  • 补码表示
    -1 的补码形式为 11111111(二进制),即 0xFF(十六进制)
    • 补码规则:绝对值取反后加1。
      1的二进制:00000001
      取反:11111110
      加1:11111111 → 即-1的补码
      
  • 内存存储
    a 的内存占用1字节(8位),直接写入 0xFF
情况2:无符号char(unsigned char)
  • 溢出处理
    若编译器默认 char 为无符号,-1 会被隐式转换为 模运算结果
    -1 mod 256 = 255 → 二进制 11111111 (0xFF)
    
  • 内存存储
    同样存入 0xFF,但程序逻辑上将其视为 255

3. 验证代码

#include <stdio.h>int main() {char a = -1;unsigned char *p = (unsigned char*)&a;printf("内存值(十六进制): 0x%02X\n", *p); // 输出 0xFFreturn 0;
}

无论 char 的默认符号性如何,输出均为 0xFF,证明内存中存储的是 11111111


4. 总结

关键点说明
内存存储值固定为 11111111(二进制)或 0xFF(十六进制)
符号性影响有符号char解释为 -1,无符号char解释为 255
编译器差异依赖默认符号性,建议显式声明 signed charunsigned char 避免歧义
补码机制负数的存储通过补码实现,保证运算一致性

5. 扩展:补码的优势

  • 统一加减法:补码允许使用同一套电路处理加减法,无需区分符号位。
  • 零的唯一性:补码中 0 仅有 00000000 一种表示,无 +0-0 歧义。
  • 范围对称性:8位有符号整数范围为 -128127,充分利用所有位组合。

通过上述分析,char a = -1; 的内存存储始终为 0xFF,其符号性由编译器决定值的解释方式。

C++实现sharedPtr,手写C++的string类

对cache的理解,如何提高缓存命中率?

数据结构中顺序存储和链式存储的优缺点

大疆车载面经

网上的参考:

自我介绍+介绍简历项目

围绕项目问涉及的知识点

问了很多DSP的内容(项目相关)

回顾你做的项目,你觉得有哪些地方可以改进?有哪些挑战?难忘的经历

说说你对大疆车载的了解,反问

优化手段有哪些

模型推理加速是怎么整的

量化部署过程

gpu加速有什么好处,原理是什么

cuda相关

多线程如何使用,痛点是什么

如何做同步,同步的手段有哪些?区别是什么?分别用在什么场景

内存优化有哪些手段?dma相关的

cache cpu 内存之间的联系

高性能面试问题

C++

  1. static的作用,修饰成员变量,成员函数。static全局变量和普通变量的区别。

  2. 怎么只在堆上创建构造函数

  3. 右值引用

  4. lambda表达式

  5. move forward

  6. 编译的过程 动态库和静态库的区别

  7. new 和 malloc的区别, new的底层实现

  8. 拷贝构造函数是传值还是引用,为什么要传引用。

  9. 线程安全的单例模式

  10. 智能指针,shared_ptr是不是线程安全的

  11. vector的扩容机制?为什么要2倍扩容

  12. C++内存模型

  13. 为什么构造函数不能是虚函数,为什么析构函数是虚函数

  14. 线程间共享内存,什么时候用到条件变量,什么时候用到锁 ,有什么区别

  15. 死锁条件,如何避免,lock_gard unquie_lock 区别

  16. C++怎么调用c语言封装的函数

  17. 多态实现,原理,虚函数表存的位置,注意别忘了模板多态,模板的偏特化

  18. 进程和线程的区别

  19. 大端小端 怎么判断,两种方法

高性能相关

  1. opencl的运行流程

  2. GPU架构

  3. GPU全局内存和局部内存区别, 怎么更好利用局部内存

  4. Cache原理

  5. 如何提高cache命中率

  6. 通常优化的思路

  7. 写opencl为什么要减少分支,掩码

  8. opencl 写kernel的主要参数有哪些

  9. 计算密集型和访存密集型的区别

  10. 可分离卷积在GPU上为什么慢,为什么是访存密集型

  11. 算子融合 conv+BN 融合的公式,为什么可以融合

  12. 推理框架中卷积的实现有哪些

  13. 时间局部性和空间局部性

  14. 产生bank confict的原因和解决方法

  15. TVM

  16. opencl 实现矩阵乘法, 向量求和

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

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

相关文章

青少年编程与数学 02-014 高中数学知识点 07课题、专业相关性分析

青少年编程与数学 02-014 高中数学知识点 07课题、专业相关性分析 一、函数与微积分1. 函数与初等函数2. 导数与优化 二、概率与统计1. 概率基础2. 统计推断3. 随机变量与分布 三、几何与代数1. 向量与矩阵运算2. 复数与坐标变换 四、数学建模与算法思维1. 数学建模2. 算法逻辑…

11乱码问题的解释(2)

这个字符串使用哪种方式编码的?---看包含在哪个文件中 和当前 mylabel.cpp 文件的编码方式是一致的~~ 如果这里显示的是 UTF-8&#xff0c;说明这个文件就是UTF-8 编码 如果显示的是 ANSI,说明这个文件就是 GBK 编码~ Qt Creator 内置的终端是 utf8 的方式来显示字符串吗?? …

我的机器学习学习之路

学习python的初衷 • hi&#xff0c;今天给朋友们分享一下我是怎么从0基础开始学习机器学习的。 • 我是2023年9月开始下定决心要学python的&#xff0c;目的有两个&#xff0c;一是为了提升自己的技能和价值&#xff0c;二是将所学的知识应用到工作中去&#xff0c;提升工作…

27--当路由器学会“防狼术“:华为设备管理面安全深度解剖(完整战备版)

当路由器学会"防狼术"&#xff1a;华为设备管理面安全深度解剖&#xff08;完整战备版&#xff09; 引言&#xff1a;网络世界的"门神"进化论 “从前有个路由器&#xff0c;它把所有数据包都当好人&#xff0c;直到有一天…” ——《悲惨世界网络版》 如果…

Docker容器网络相关设置

确认容器是否正确启动 首先&#xff0c;确保 MySQL 容器正在运行。可以使用 docker ps 查看当前正在运行的容器。如果 MySQL 容器没有启动&#xff0c;可以尝试以下命令启动它&#xff1a; docker run -d --name mysql-container -e MYSQL_ROOT_PASSWORDrootpassword mysql:8 这…

hive相关面试题以及答案

什么是Hive&#xff1f;它的作用是什么&#xff1f; 答&#xff1a;Hive是一个建立在Hadoop之上的数据仓库工具&#xff0c;它提供了类似于SQL的查询语言HiveQL来操作存储在Hadoop中的数据。Hive的主要作用是让用户能够使用SQL语法来查询和分析大规模数据集。 Hive的架构是什么…

前端学习记录之HTML

1. 网页 1.1 什么是网页 网站是指在因特网上根据一定的规则&#xff0c;使用HTML等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”&#xff0c;通常是HTML格式的文件&#xff0c;它要通过浏览器来阅读 网页是构成网站的基本元素。它通常由图片&#xff0c;…

【1-1】ICT=IT+CT

前言 从这篇文章开始&#xff0c;我将总结软考网工相关的笔记和自己的所思所想。我所总结内容均来自互联网&#xff0c;欢迎大家交流、学习、讨论。 1. ICT ICT IT CT 这里&#xff0c;这三个缩写的对应英文如下&#xff1a; 缩写英文含义ICTInformation and Communicat…

多账号安全登录与浏览器指纹管理的实现方案

随着跨境电商、社交媒体运营等场景的普及&#xff0c;用户对多账号管理与反检测技术的需求日益增长。指纹浏览器作为一款专注于多账号安全登录与浏览器指纹管理的工具&#xff0c;通过虚拟浏览器环境隔离、动态指纹模拟等技术&#xff0c;解决了账号关联封禁的痛点。本文将从技…

CMake Presets教程

在使用 CMake 作为构建工具的时候, 对于一个稍微大一点的项目, 存在有很多的选项. 比如 Debug 版本还是 Release 版本, 是否开启特定选项, 是否开启测试等等. 这些通常是作为命令行参数传递进去的. 但是很多程序员并不在命令行中作开发, 更多的是使用 IDE 来进行开发. 不同的 I…

vue搭建一个树形菜单项目

首先搭建项目需要先通过步骤搭建一个vue的项目&#xff0c;然后创建一个component文件&#xff0c;里面新建一个index.vue页面来。 这是引入的element-ui组件库里的组件&#xff0c;来实现我的路由&#xff0c;渲染的是我存储的动态路由&#xff0c;所以需要先安装并且引用。 …

【Python 算法】动态规划

本博客笔记内容来源于灵神&#xff0c;视频链接如下&#xff1a;https://www.bilibili.com/video/BV16Y411v7Y6?vd_source7414087e971fef9431117e44d8ba61a7&spm_id_from333.788.player.switch 01背包 计算了f[i1]&#xff0c;f[i]就没用了&#xff0c;相当于每时每刻只有…

c#的反射和特性

在 C# 中&#xff0c;反射&#xff08;Reflection&#xff09;和特性&#xff08;Attributes&#xff09;是两个强大的功能&#xff0c;它们在运行时提供元编程能力&#xff0c;广泛用于框架开发、对象映射和动态行为扩展。以下是对它们的详细介绍&#xff0c;包括定义、用法、…

云终端的作用,此刻在校园和医院里具象化

数字化转型已经成为各行各业交流的热点话题&#xff0c;校园和医院这两个重要领域正经历着深刻变革。云终端&#xff0c;正以实际应用成果展现其独特作用&#xff0c;让人们切实感受到它带来的高效与便利。 传统的教学中&#xff0c;学校机房的电脑设备更新换代成本高&#xf…

UniApp快速表单组件

环境&#xff1a;vue3 uni-app 依赖库&#xff1a;uview-plus、dayjs 通过配置项快速构建 form 表单 使用 <script setup>import CustomCard from /components/custom-card.vue;import { ref } from vue;import CustomFormItem from /components/form/custom-form-it…

Android: Handler 的用法详解

Android 中 Handler 的用法详解 Handler 是 Android 中用于线程间通信的重要机制&#xff0c;主要用于在不同线程之间发送和处理消息。以下是 Handler 的全面用法指南&#xff1a; 一、Handler 的基本原理 Handler 基于消息队列(MessageQueue)和循环器(Looper)工作&#xff…

UE5学习笔记 FPS游戏制作33 游戏保存

文章目录 核心思想创建数据对象创建UIUI参数和方法打开UI存档文件的位置可以保存的数据类型 核心思想 UE自己有保存游戏的功能&#xff0c;核心节点&#xff0c;类似于json操作&#xff0c;需要一个数据类的对象来进行保存和读取 创建存档 加载存档 保存存档 创建数据对象…

【蓝桥杯】 枚举和模拟练习题

系列文章目录 蓝桥杯例题 枚举和模拟 文章目录 系列文章目录前言一、好数&#xff1a; 题目参考&#xff1a;核心思想&#xff1a;代码实现&#xff1a; 二、艺术与篮球&#xff1a; 题目参考&#xff1a;核心思想&#xff1a;代码实现: 总结 前言 今天距离蓝桥杯还有13天&…

大数据技术之Scala:特性、应用与生态系统

摘要 Scala 作为一门融合面向对象编程与函数式编程范式的编程语言&#xff0c;在大数据领域展现出独特优势。本文深入探讨 Scala 的核心特性&#xff0c;如函数式编程特性、类型系统以及与 Java 的兼容性等。同时&#xff0c;阐述其在大数据处理框架&#xff08;如 Apache Spa…

Linux信号——信号的产生(1)

注&#xff1a;信号vs信号量&#xff1a;两者没有任何关系&#xff01; 信号是什么&#xff1f; Linux系统提供的&#xff0c;让用户&#xff08;进程&#xff09;给其他进程发送异步信息的一种方式。 进程看待信号的方式&#xff1a; 1.信号在没有发生的时候&#xff0c;进…