游戏引擎学习第五天

news2024/11/24 9:27:56

这节貌似没讲什么
视频参考:https://www.bilibili.com/video/BV1Gmm2Y5EwE/

uint8 *A = somewhere in memory;
uint8 *B = somewhere in memory;

//BEFORE WE GOT TO HERE
int Y = *B; // == whatever was actually there before the 5
*A = 5;
int X = *B; // == 5
//Obviously! Y and X 
//DUH? The compiler should just load it once

在你的代码示例中:

以下是对每部分的解释:

  • uint8 *Auint8 *B:这两个是指向内存位置的指针,指向某个内存区域。具体的内存地址在代码中没有明确给出,因此我们不清楚它们指向的具体内存。

  • int Y = *B;:这行代码将指针 B 指向的内存位置的值解引用(即读取 *B)并将其赋值给 Y

  • *A = 5;:这行代码将指针 A 指向的内存位置的值设置为 5

  • int X = *B;:最后,代码再次解引用指针 B,将其值赋给 X

问题:为什么编译器不应该重新加载 *B,而是直接使用 Y 的值?

  • “编译器应该只加载一次”: 这段注释表示,从理论上讲,一旦 *B 的值被加载到 Y 中,之后不应该再次加载,因为在代码中并没有直接修改 B 指向的内存。因此,编译器本可以通过将 *B 的值存储到临时变量中,从而避免在 X 的赋值中再次解引用 B

为什么这种优化可能没有发生?

  1. 内存访问优化: 编译器在优化内存访问时,通常假设内存可能被程序的其他部分修改,尤其是如果这些内存可能是共享的。比如,AB 如果指向同一内存区域,或者内存的值可能被其他线程或硬件修改,编译器就不能假设 *B 在两次访问之间没有发生变化。

  2. 指针别名(Pointer Aliasing): 如果 AB 是指向同一块内存区域的指针,编译器不能假设 *B 的值在两次解引用之间不变,因为对 *A 的赋值可能会影响到 *B(例如,AB 指向同一内存)。因此,编译器不能简单地优化掉第二次对 *B 的解引用。

  3. 优化标志: 现代编译器有不同的优化等级,某些优化(如这类优化)只有在设置了较高的优化标志时才会执行(例如,GCC 中的 -O2-O3)。如果没有进行优化,或者优化级别较低(如 -O0),编译器可能不会执行这种优化。

  4. 内存的易变性(Volatility): 如果 AB 指向的内存被标记为 volatile,编译器就会强制每次都解引用它们,而不进行任何假设或优化。volatile 常用于与内存映射的 I/O、硬件寄存器或并发环境中的共享内存交互。

总结

简单来说,编译器 可以 通过将 *B 的值存储到一个临时变量中,避免第二次解引用 *B(即优化掉这次内存加载),前提是 编译器能够保证 *B 的值在两次解引用之间没有改变。然而,由于指针别名、并发操作或其他潜在的副作用,编译器可能不能进行这种优化。编译器是否进行优化取决于它对内存模型的理解以及它是否能保证两次解引用之间 *B 的值不变。

在这段代码中,MSG Message; 的声明位置取决于你对 MSG 变量的作用范围的需求。一般来说,建议将其声明放在 while 循环内部,而不是外部,原因如下:

  1. 局部性和性能:
    如果将 MSG 声明放在循环外部,MSG 变量将在每次循环时保持其值,可能导致不必要的残留数据。如果将其放在循环内部,每次循环都会创建一个新的 MSG 变量,避免了不必要的状态残留。

  2. 清晰的作用范围:
    MSG Message; 放在循环内部可以限制其作用范围,使得它只在当前的循环中有效。这使得代码更加清晰,并且减少了作用域问题。例如,如果你在多个地方使用 Message,会更容易理解它的生命周期和作用域。

  3. 避免潜在的逻辑错误:
    如果 MSG Message; 放在循环外部,它可能会在消息队列没有更新时继续保留上一个循环的消息,这可能会导致错误的行为。将其放在内部能确保每次迭代都能正确接收和处理新的消息。

因此,将 MSG Message; 放在 while 循环内部是更好的选择。你的代码可以这样优化:

if (Window) { // 检查窗口句柄是否有效,若有效则进入消息循环
  int xOffset = 0;
  int yOffset = 0;
  Running = true;
  while (Running) { // 启动一个无限循环,等待和处理消息
    MSG Message;    // 将 MSG 声明移至 while 循环内部
    while (PeekMessage(
        &Message,
        0, // hWnd 为 NULL,则检查当前线程中所有窗口的消息
        0, 0, // 用于设定消息类型的范围
        PM_REMOVE // 将消息从消息队列中移除,类似于 GetMessage 的行为
        )) {
      if (Message.message == WM_QUIT) {
        Running = false;
      }
      TranslateMessage(&Message); // 翻译消息,如果是键盘消息需要翻译
      DispatchMessage(&Message); // 分派消息,调用窗口过程处理消息
    }

    RenderWeirdGradient(xOffset, yOffset); // 渲染

    {
      HDC DeviceContext = GetDC(Window);
      RECT WindowRect;
      GetClientRect(Window, &WindowRect);
      int WindowWidth = WindowRect.right - WindowRect.left;
      int WindowHeight = WindowRect.bottom - WindowRect.top;
      Win32UpdateWindow(DeviceContext, &WindowRect, 0, 0, WindowWidth, WindowHeight);
      ReleaseDC(Window, DeviceContext);
    }

    ++xOffset;
  }
}

这样可以确保每次循环都从消息队列中正确获取新的消息,并且 MSG 变量的作用范围是局部的。

根据您提供的栈中变量地址和内容,可以绘制出堆栈结构图,展示各个局部变量如何在栈帧中存储。以下是一个简化的堆栈结构图:

栈结构图

+----------------------------------------------------+  <- 栈顶
|                    返回地址                        |  
+----------------------------------------------------+  
|                参数 cmdshow (int *)                |  <- 参数 cmdshow 存储在此,指向栈上的一个 int 值
|                0x000000721a8ffbc8                   |  
+----------------------------------------------------+  
|                参数 cmdline (char **)              |  <- 参数 cmdline 存储在此,指向栈上的一个字符串
|                0x000000721a8ffbc0                   |  
+----------------------------------------------------+  
|           参数 hInstPrev (HINSTANCE__ **)          |  <- 参数 hInstPrev 存储在此,指向一个空指针
|                0x000000721a8ffbb8                   |  
+----------------------------------------------------+  
|           参数 hInst (HINSTANCE__ **)               |  <- 参数 hInst 存储在此,指向一个有效的 HINSTANCE
|                0x000000721a8ffbb0                   |  
+----------------------------------------------------+  
|              WindowClass (WNDCLASS)                |  <- WindowClass 存储在此
|                0x000000721a8ff2f0                   |  
|                style = 0                           |  
|                lpfnWndProc = 0x0000000000000000     |  
|                cbClsExtra = 0                      |  
|                ...                                 |  
+----------------------------------------------------+  
|           BigOldBlockOfMemory (1024KB)             |  <- 大的内存块,占用 1024KB
|                0x000000721a8042d0                   |  
|                [1024KB 内存空间]                   |  <- 1MB 数据
+----------------------------------------------------+  <- 栈底

在这里插入图片描述

解释:

  1. 栈顶(返回地址):函数 WinMain 返回时,程序会跳到此地址。
  2. 参数 cmdshow, cmdline, hInstPrev, hInst:这些参数的值存储在栈帧中,按顺序排列:
    • cmdshow 存储了一个 int * 类型的地址,指向栈上的值 10
    • cmdline 存储了一个 char ** 类型的地址,指向栈上的字符串(为空字符串)。
    • hInstPrev 存储了一个 HINSTANCE__ ** 类型的地址,指向 NULL
    • hInst 存储了一个 HINSTANCE__ ** 类型的地址,指向有效的程序映像基址 game.exe
  3. WindowClass:是一个 WNDCLASS 结构体,包含多种成员(如 style, lpfnWndProc 等)。每个成员被初始化为零或 nullptr
  4. BigOldBlockOfMemory:这是一个 1MB 大小的 uint8 数组,栈上分配。它存储在 BigOldBlockOfMemory 地址处,内部包含 1024KB 的数据。

注意:

  • 栈分配顺序:栈的分配是从高地址向低地址方向进行的,所以栈顶的变量(返回地址)先存储,然后是函数参数,最后是局部变量(如 WindowClassBigOldBlockOfMemory)。
  • 指针的存储:对于指针类型的变量(如 cmdshow, cmdline, hInstPrev, hInst),它们存储的是指向实际数据的地址。这些数据的内容在栈帧中的其他位置。
// game.cpp : Defines the entry point for the application.
//

#include <cstdint>
#include <stdint.h>
#include <windows.h>

#define internal static        // 用于定义内翻译单元内部函数
#define local_persist static   // 局部静态变量
#define global_variable static // 全局变量

typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;

typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;

struct win32_offscreen_buffer {
  BITMAPINFO Info;
  void *Memory;
  // 后备缓冲区的宽度和高度
  int Width;
  int Height;
  int Pitch;
  int BytesPerPixel;
};

// TODO: 全局变量
global_variable bool Running;
global_variable win32_offscreen_buffer GlobalBackbuffer;

// 添加这个去掉重复的冗余代码
struct win32_window_dimension {
  int Width;
  int Height;
};

win32_window_dimension Win32GetWindowDimension(HWND Window) {
  win32_window_dimension Result;
  RECT ClientRect;
  GetClientRect(Window, &ClientRect);
  // 计算绘制区域的宽度和高度
  Result.Height = ClientRect.bottom - ClientRect.top;
  Result.Width = ClientRect.right - ClientRect.left;
  return Result;
}

// 渲染一个奇异的渐变图案
internal void RenderWeirdGradient(win32_offscreen_buffer Buffer, int BlueOffset,
                                  int GreenOffset) {
  // TODO:让我们看看优化器是怎么做的
  uint8 *Row = (uint8 *)Buffer.Memory;      // 指向位图数据的起始位置
  for (int Y = 0; Y < Buffer.Height; ++Y) { // 遍历每一行
    uint32 *Pixel = (uint32 *)Row;          // 指向每一行的起始像素
    for (int X = 0; X < Buffer.Width; ++X) { // 遍历每一列
      uint8 Blue = (X + BlueOffset);         // 计算蓝色分量
      uint8 Green = (Y + GreenOffset);       // 计算绿色分量
      *Pixel++ = ((Green << 8) | Blue);      // 设置当前像素的颜色
    }
    Row += Buffer.Pitch; // 移动到下一行
  }
}

// 这个函数用于重新调整 DIB(设备独立位图)大小
internal void Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int width,
                                    int height) {
  // device independent bitmap(设备独立位图)
  // TODO: 进一步优化代码的健壮性
  // 可能的改进:先不释放,先尝试其他方法,再如果失败再释放。
  if (Buffer->Memory) {
    VirtualFree(
        Buffer->Memory, // 指定要释放的内存块起始地址
        0, // 要释放的大小(字节),对部分释放有效,整体释放则设为 0
        MEM_RELEASE); // MEM_RELEASE:释放整个内存块,将内存和地址空间都归还给操作系统
  }
  // 赋值后备缓冲的宽度和高度
  Buffer->Width = width;
  Buffer->Height = height;
  Buffer->BytesPerPixel = 4;

  // 设置位图信息头(BITMAPINFOHEADER)
  Buffer->Info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); // 位图头大小
  Buffer->Info.bmiHeader.biWidth = Buffer->Width; // 设置位图的宽度
  Buffer->Info.bmiHeader.biHeight =
      -Buffer->Height; // 设置位图的高度(负号表示自上而下的方向)
  Buffer->Info.bmiHeader.biPlanes = 1; // 设置颜色平面数,通常为 1
  Buffer->Info.bmiHeader.biBitCount =
      32; // 每像素的位数,这里为 32 位(即 RGBA)
  Buffer->Info.bmiHeader.biCompression =
      BI_RGB; // 无压缩,直接使用 RGB 颜色模式

  // 创建 DIBSection(设备独立位图)并返回句柄
  // TODO:我们可以自己分配?
  int BitmapMemorySize =
      (Buffer->Width * Buffer->Height) * Buffer->BytesPerPixel;
  Buffer->Memory = VirtualAlloc(
      0, // lpAddress:指定内存块的起始地址。
         // 通常设为 NULL,由系统自动选择一个合适的地址。
      BitmapMemorySize, // 要分配的内存大小,单位是字节。
      MEM_COMMIT, // 分配物理内存并映射到虚拟地址。已提交的内存可以被进程实际访问和操作。
      PAGE_READWRITE // 内存可读写
  );
  Buffer->Pitch = width * Buffer->BytesPerPixel; // 每一行的字节数
  // TODO:可能会把它清除成黑色
}

// 这个函数用于将 DIBSection 绘制到窗口设备上下文
internal void Win32DisplayBufferInWindow(HDC DeviceContext, int WindowWidth,
                                         int WindowHeight,
                                         win32_offscreen_buffer Buffer, int X,
                                         int Y, int Width, int Height) {
  // 使用 StretchDIBits 将 DIBSection 绘制到设备上下文中
  StretchDIBits(
      DeviceContext, // 目标设备上下文(窗口或屏幕的设备上下文)
      /*
      X, Y, Width, Height, // 目标区域的 x, y 坐标及宽高
      X, Y, Width, Height,
      */
      0, 0, WindowWidth, WindowHeight,   //
      0, 0, Buffer.Width, Buffer.Height, //
      // 源区域的 x, y 坐标及宽高(此处源区域与目标区域相同)
      Buffer.Memory,  // 位图内存指针,指向 DIBSection 数据
      &Buffer.Info,   // 位图信息,包含位图的大小、颜色等信息
      DIB_RGB_COLORS, // 颜色类型,使用 RGB 颜色
      SRCCOPY); // 使用 SRCCOPY 操作符进行拷贝(即源图像直接拷贝到目标区域)
}

LRESULT CALLBACK
Win32MainWindowCallback(HWND hwnd, // 窗口句柄,表示消息来源的窗口
                        UINT Message, // 消息标识符,表示当前接收到的消息类型
                        WPARAM wParam, // 与消息相关的附加信息,取决于消息类型
                        LPARAM LParam) { // 与消息相关的附加信息,取决于消息类型
  LRESULT Result = 0; // 定义一个变量来存储消息处理的结果

  switch (Message) { // 根据消息类型进行不同的处理
  case WM_CREATE: {
    OutputDebugStringA("WM_CREATE\n");
  };
  case WM_SIZE: { // 窗口大小发生变化时的消息
  } break;

  case WM_DESTROY: { // 窗口销毁时的消息
    // TODO: 处理错误,用重建窗口
    Running = false;
  } break;

  case WM_CLOSE: { // 窗口关闭时的消息
    // TODO: 像用户发送消息进行处理
    Running = false;
  } break;

  case WM_ACTIVATEAPP: { // 应用程序激活或失去焦点时的消息
    OutputDebugStringA(
        "WM_ACTIVATEAPP\n"); // 输出调试信息,表示应用程序激活或失去焦点
  } break;

  case WM_PAINT: { // 处理 WM_PAINT 消息,通常在窗口需要重新绘制时触发
    PAINTSTRUCT Paint; // 定义一个 PAINTSTRUCT 结构体,保存绘制的信息
    // 调用 BeginPaint 开始绘制,并获取设备上下文 (HDC),同时填充 Paint 结构体
    HDC DeviceContext = BeginPaint(hwnd, &Paint);
    // 获取当前绘制区域的左上角坐标
    int X = Paint.rcPaint.left;
    int Y = Paint.rcPaint.top;

    // 计算绘制区域的宽度和高度
    int Height = Paint.rcPaint.bottom - Paint.rcPaint.top;
    int Width = Paint.rcPaint.right - Paint.rcPaint.left;

    win32_window_dimension Dimension = Win32GetWindowDimension(hwnd);

    Win32DisplayBufferInWindow(DeviceContext, Dimension.Width, Dimension.Height,
                               GlobalBackbuffer, X, Y, Width, Height);

#if 0
    local_persist DWORD Operation = WHITENESS;

    // 使用 WHITENESS 操作符填充矩形区域为白色
    PatBlt(DeviceContext, X, Y, Width, Height, Operation);

    // 设置窗体的颜色在刷新时白色和黑色之间来回变换
    if (Operation == WHITENESS) {
      Operation = BLACKNESS;
    } else {
      Operation = WHITENESS;
    }
#endif

    // 调用 EndPaint 结束绘制,并释放设备上下文
    EndPaint(hwnd, &Paint);
  } break;

  default: { // 对于不处理的消息,调用默认的窗口过程
    Result = DefWindowProc(hwnd, Message, wParam,
                           LParam); // 调用默认窗口过程处理消息
  } break;
  }

  return Result; // 返回处理结果
}

int CALLBACK WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, //
                     PSTR cmdline, int cmdshow) {
  uint8 BigOldBlockOfMemory[1004 * 1024];
  WNDCLASS WindowClass = {};
  // 使用大括号初始化,所有成员都被初始化为零(0)或 nullptr

  Win32ResizeDIBSection(&GlobalBackbuffer, 1280, 720);

  // WindowClass.style:表示窗口类的样式。通常设置为一些 Windows
  // 窗口样式标志(例如 CS_HREDRAW, CS_VREDRAW)。
  WindowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
  // CS_HREDRAW 当窗口的宽度发生变化时,窗口会被重绘。
  // CS_VREDRAW 当窗口的高度发生变化时,窗口会被重绘

  //  WindowClass.lpfnWndProc:指向窗口过程函数的指针,窗口过程用于处理与窗口相关的消息。
  WindowClass.lpfnWndProc = Win32MainWindowCallback;

  // WindowClass.hInstance:指定当前应用程序的实例句柄,Windows
  // 应用程序必须有一个实例句柄。
  WindowClass.hInstance = hInst;

  // WindowClass.lpszClassName:指定窗口类的名称,通常用于创建窗口时注册该类。
  WindowClass.lpszClassName = "gameWindowClass"; // 类名
  if (RegisterClass(&WindowClass)) {             // 如果窗口类注册成功
    HWND Window = CreateWindowEx(
        0,                         // 创建窗口,使用扩展窗口风格
        WindowClass.lpszClassName, // 窗口类的名称,指向已注册的窗口类
        "game",                    // 窗口标题(窗口的名称)
        WS_OVERLAPPEDWINDOW |
            WS_VISIBLE, // 窗口样式:重叠窗口(带有菜单、边框等)并且可见
        CW_USEDEFAULT, // 窗口的初始位置:使用默认位置(X坐标)
        CW_USEDEFAULT, // 窗口的初始位置:使用默认位置(Y坐标)
        CW_USEDEFAULT, // 窗口的初始宽度:使用默认宽度
        CW_USEDEFAULT, // 窗口的初始高度:使用默认高度
        0,             // 父窗口句柄(此处无父窗口,传0)
        0,             // 菜单句柄(此处没有菜单,传0)
        hInst,         // 当前应用程序的实例句柄
        0 // 额外的创建参数(此处没有传递额外参数)
    );
    // 如果窗口创建成功,Window 将保存窗口的句柄
    if (Window) { // 检查窗口句柄是否有效,若有效则进入消息循环
      int xOffset = 0;
      int yOffset = 0;
      Running = true;
      while (Running) { // 启动一个无限循环,等待和处理消息
        MSG Message;    // 声明一个 MSG 结构体,用于接收消息
        while (PeekMessage(
            &Message,
            // 指向一个 `MSG` 结构的指针。`PeekMessage`
            // 将在 `lpMsg` 中填入符合条件的消息内容。
            0,
            // `hWnd` 为`NULL`,则检查当前线程中所有窗口的消息;
            // 如果设置为特定的窗口句柄,则只检查该窗口的消息。
            0, //
            0, // 用于设定消息类型的范围
            PM_REMOVE // 将消息从消息队列中移除,类似于 `GetMessage` 的行为。
            )) {
          if (Message.message == WM_QUIT) {
            Running = false;
          }
          TranslateMessage(&Message); // 翻译消息,如果是键盘消息需要翻译
          DispatchMessage(&Message); // 分派消息,调用窗口过程处理消息
        }
        RenderWeirdGradient(GlobalBackbuffer, xOffset, yOffset);
        // 这个地方需要渲染一下不然是黑屏
        {
          HDC DeviceContext = GetDC(Window);

          win32_window_dimension Dimension = Win32GetWindowDimension(Window);

          RECT WindowRect;
          GetClientRect(Window, &WindowRect);
          int WindowWidth = WindowRect.right - WindowRect.left;
          int WindowHeigh = WindowRect.bottom - WindowRect.top;
          Win32DisplayBufferInWindow(DeviceContext, Dimension.Width,
                                     Dimension.Height, GlobalBackbuffer, 0, 0,
                                     WindowWidth, WindowHeigh);

          ReleaseDC(Window, DeviceContext);
        }
        ++xOffset;
      }
    } else { // 如果窗口创建失败
             // 这里可以处理窗口创建失败的逻辑
             // 比如输出错误信息,或退出程序等
             // TODO:
    }
  } else { // 如果窗口类注册失败
           // 这里可以处理注册失败的逻辑
           // 比如输出错误信息,或退出程序等
           // TODO:
  }

  return 0;
}

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

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

相关文章

Linux——基础指令2 + 权限

目录 1.zip/unzip 2.tar 3.bc 4.uname –r 5.重要的几个热键 6.扩展命令 7.shell命令以及运行原理 8.Linux权限的理解 关于权限的三个问题&#xff1a; 1.目录权限 2.缺省权限 3.粘滞位 1.zip/unzip 打包、压缩&#xff1a;使用特定的算法&#xff0c;文件进行合…

摄像机视频分析软件下载LiteAIServer视频智能分析软件抖动检测的技术实现

在现代社会中&#xff0c;视频监控系统扮演着至关重要的角色&#xff0c;其可靠性和有效性在很大程度上取决于视频质量。然而&#xff0c;由于多种因素&#xff0c;如摄像机安装不当、外部环境振动或视频信号传输的不稳定&#xff0c;视频画面常常出现抖动问题&#xff0c;这不…

Pandas | 数据分析时将特定列转换为数字类型 float64 或 int64的方法

类型转换 传统方法astype使用value_counts统计通过apply替换并使用astype转换 pd.to_numericx对连续变量进行转化⭐参数&#xff1a;返回值&#xff1a;示例代码&#xff1a; isnull不会检查空字符串 数据准备 有一组数据信息如下&#xff0c;其中主要将TotalCharges、MonthlyC…

Fish Agent V0.13B:Fish Audio的语音处理新突破,AI语音助手的未来已来!

近日&#xff0c;Fish Audio公司发布了一款全新的语音处理模型——Fish Agent V0.13B&#xff0c;这款模型以其高效、精确的语音生成和处理能力&#xff0c;尤其是在模拟或克隆不同声音方面的表现&#xff0c;引起了广泛关注。这不仅意味着我们在拥有一个声音自然、反应迅速的A…

稀疏视角CBCT重建的几何感知衰减学习|文献速递-基于深度学习的病灶分割与数据超分辨率

Title 题目 Geometry-Aware Attenuation Learning forSparse-View CBCT Reconstruction 稀疏视角CBCT重建的几何感知衰减学习 01 文献速递介绍 稀疏视角锥形束计算机断层扫描&#xff08;CBCT&#xff09;重建的几何感知学习方法 锥形束计算机断层扫描&#xff08;CBCT&a…

Docker入门系列——Docker-Compose

Docker Compose 是 Docker 官方编排工具&#xff0c;用于定义和运行多容器 Docker 应用程序。它是一个轻量级的工具&#xff0c;用于快速配置和启动应用程序的不同服务。 Docker Compose 是什么 Docker Compose 最初是由 Docker 公司开发&#xff0c;并于 2014 年 6 月首次发布…

[运维][Nginx]Nginx学习(1/5)--Nginx基础

Nginx简介 背景介绍 Nginx一个具有高性能的【HTTP】和【反向代理】的【WEB服务器】&#xff0c;同时也是一个【POP3/SMTP/IMAP代理服务器】&#xff0c;是由伊戈尔赛索耶夫(俄罗斯人)使用C语言编写的&#xff0c;Nginx的第一个版本是2004年10月4号发布的0.1.0版本。另外值得一…

GIN:逼近WL-test的GNN架构

Introduction 在 图卷积网络GCN 中我们已经知道图神经网络在结点分类等任务上的作用&#xff0c;但GIN&#xff08;图同构神经网络&#xff09;给出了一个对于图嵌入&#xff08;graph embedding&#xff09;更强的公式。 GIN&#xff0c;图同构神经网络&#xff0c;致力于解…

ReactPress与WordPress:一场内容管理系统的较量

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress WordPress官网&#xff1a;https://wordpress.org/ ReactPress与WordPress&#xff1a;一场内容管理系统的较量 在当今数字化时代&#xff0c;内容管理系统&#xff08;CMS&#xff09;已成为…

红日靶机(七)笔记

VulnStack-红日靶机七 概述 在 VulnStack7 是由 5 台目标机器组成的三层网络环境&#xff0c;分别为 DMZ 区、第二层网络、第三层网络。涉及到的知识点也是有很多&#xff0c;redis未授权的利用、laravel的历史漏洞、docker逃逸、隧道、代理的搭建、通达OA系统的历史漏洞、ms…

【bat】自动生成指定层级文件夹

&#x1f305; 一日之计在于晨&#xff0c;启航新程 ⭐ 本期特辑&#xff1a;自动生成指定层级文件夹 &#x1f3c6; 系列专题&#xff1a;BAT脚本工坊 文章目录 前言批处理脚本介绍脚本执行过程总结 前言 在日常的计算机使用过程中&#xff0c;我们经常需要创建文件夹来组织和…

45.第二阶段x86游戏实战2-hook监控实时抓取游戏lua

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

ISAAC SIM踩坑记录--ROS2相机影像发布

其实这个例子官方和大佬NVIDIA Omniverse和Isaac Sim笔记5&#xff1a;Isaac Sim的ROS接口与相机影像、位姿真值发布/保存都已经有详细介绍了&#xff0c;但是都是基于ROS的&#xff0c;现在最新的已经是ROS2&#xff0c;这里把不同的地方简单记录一下。 搭建一个简单的场景&a…

MySQL技巧之跨服务器数据查询:基础篇-A数据库与B数据库查询合并

MySQL技巧之跨服务器数据查询&#xff1a;基础篇-A数据库与B数据库查询合并 上一篇已经描述&#xff1a;借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MySQL数据库的链接名: MY_ODBC_MYSQL 以…

问:MySQL主从同步的机制梳理?

MySQL主从复制是一种数据库复制技术&#xff0c;通过将一个MySQL数据库服务器&#xff08;主节点&#xff09;的数据复制到一个或多个其他MySQL数据库服务器&#xff08;从节点&#xff09;&#xff0c;实现数据的自动同步。这种技术不仅提高了数据的可用性&#xff0c;还能通过…

物联网低功耗广域网LoRa开发(一):LoRa物联网行业解决方案

一、LoRa的优势以及与其他无线通信技术对比 &#xff08;一&#xff09;LoRa的优势 1、164dB链路预算 、距离>15km 2、快速、灵活的基础设施易组网且投资成本较少 3、LoRa节点模块仅用于通讯电池寿命长达10年 4、免牌照的频段 网关/路由器建设和运营 、节点/终端成本低…

【Linux】sudo make install 命令往系统中安装了什么 指定目录进行安装

前情提要 假如我们通过源码安装的方式&#xff0c;安装一个动态库&#xff0c;风格往往是这样的&#xff1a; # 克隆仓库 git clone https://github.com/xxx.git# 进入仓库目录 cd xxx编译 # ... 可能有一些校验代码完整性的sh命令# 构建 mkdir build cd build cmake ..# 编…

基于YOLOv5的人群密度检测系统设计与实现

大家好&#xff0c;本文将介绍基于改进后的YOLOv5目标检测模型&#xff0c;设计并实现人群密度检测系统。 使用YOLOv5的源代码&#xff0c;在此基础上修改和训练模型&#xff0c; 数据集选用crowdhuman数据集。对yolov5源码中的文件进行修改&#xff0c;更换主干网络、改进损失…

zabbix搭建钉钉告警流程

目录 zabbix实验规划 zabbix实验步骤 1 使用钉钉添加一个自定义的机器人 ​编辑2在zabbix-server上编写钉钉信息发送脚本&#xff0c;设置钉钉报警媒介 设置钉钉报警媒介​编辑​编辑 在添加消息模板​编辑​编辑​编辑 3设置动作条件 触发后的行为&#xff1a;重新添加一…

在 Oracle Linux 8.9 上安装Oracle Database 23ai 23.5

在 Oracle Linux 8.9 上安装Oracle Database 23ai 23.5 1. 安装 Oracle Database 23ai2. 连接 Oracle Database 23c3. 重启启动后&#xff0c;手动启动数据库4. 重启启动后&#xff0c;手动启动 Listener5. 手动启动 Pluggable Database6. 自动启动 Pluggable Database7. 设置开…