https://blog.csdn.net/weixin_48896613/article/details/127371045(内存对齐)

简介:C语言命名规范,内存管理与错误处理,C语言数据类型与内存布局,C语言数据类型转换与精度丢失。

微软命名规范

微软(Microsoft)有严格的编程命名规范,以确保代码的可读性和一致性。以下是一些主要的命名规范和最佳实践:

一般原则

  1. 可读性:命名应简洁明了,便于理解。

  2. 一致性:保持命名风格的一致性,遵循团队和项目的规范。

  3. 描述性:命名应描述变量、函数、类等的用途或功能。

类、接口和结构(都使用驼峰体)

  • 类名:使用 PascalCase(首字母大写的驼峰式),例如 CustomerOrder

  • 接口名:使用 PascalCase,并以 I 开头,例如 ICustomerRepository

  • 结构名:与类名相同,使用 PascalCase,例如 CustomerRecord

方法和属性

  • 方法名:使用 PascalCase,方法名应为动词或动词短语,例如 CalculateTotal

  • 属性名:使用 PascalCase,属性名应为名词或名词短语,例如 OrderDate

变量和字段

  • 局部变量:使用 camelCase(首字母小写的驼峰式),例如 orderTotal

  • 私有字段:使用 camelCase,并以下划线 _ 开头,例如 _customerName

常量

  • 常量名:使用 PascalCase,例如 MaxOrderQuantity。对于常量,所有字母都大写并使用下划线分隔单词,例如 MAX_ORDER_QUANTITY

参数

  • 参数名:使用 camelCase,参数名应为描述性名词,例如 customerName

其他

  • 缩写和首字母缩略词:当缩写在标识符中时,使用 PascalCase 或 camelCase 处理,例如 XmlParserparseXml

变量

//全局变量前面加g_

示例代码

public interface ICustomerRepository
{
    Customer GetCustomerById(int customerId);
    void AddCustomer(Customer customer);
}

public class CustomerOrder
{
    private string _customerName;
    private DateTime _orderDate;

    public string CustomerName
    {
        get { return _customerName; }
        set { _customerName = value; }
    }

    public DateTime OrderDate
    {
        get { return _orderDate; }
        set { _orderDate = value; }
    }

    public void CalculateTotal()
    {
        // Implementation here
    }
}

参考资料

#include <stdlib.h>
#include <stdio.h>

int main(){

  char szBuf[20] = {0};
  scanf("%19[0-9]s", szBuf);//0到9
  scanf("%19[0, 9, a]s", szBuf);//只有0 9 a
  scanf("%19[^8]s", szBuf);//排除8
  //scanf的正则

  system("pause");
    return 0;
}

编译器分配局部变量

编译器在分配局部变量时,通常会依据一些规则和优化策略。以下是一些常见的规则和因素:

  1. 栈帧布局: 局部变量通常存储在栈上。每个函数调用都会在栈上创建一个栈帧,栈帧包括局部变量、返回地址和一些其他状态信息。栈帧的大小和布局由编译器决定,通常会根据变量声明的顺序进行分配。

  2. 变量声明顺序: 通常情况下,编译器会按照变量在代码中声明的顺序分配内存。即先声明的变量会被分配到较低的内存地址,而后声明的变量会被分配到较高的内存地址(在栈向下增长的系统中)。

  3. 对齐要求: 编译器会考虑变量的对齐要求。某些类型的数据(如 intdouble 等)需要在特定的内存地址对齐。编译器会插入适当的填充字节(padding)来满足这些对齐要求。

  4. 优化策略: 编译器在优化代码时可能会改变变量的分配顺序和位置。例如,它可能会将不常使用的变量分配到离栈指针更远的位置,或将常用的变量保存在寄存器中,而不是在栈上。

  5. 函数调用约定: 不同的系统和编译器可能有不同的函数调用约定,这些约定会影响栈帧的布局。例如,有些约定要求保存调用者的寄存器,有些约定则要求保存被调用者的寄存器。

  6. 局部变量的生命周期: 编译器会考虑局部变量的生命周期,尽量重用栈空间。比如,一个变量在一个作用域内有效,当离开该作用域时,其占用的栈空间可能会被后续的变量使用。

示例代码

让我们通过一个示例代码来展示局部变量的内存分配:

#include <stdio.h>

void func() {
    int a = 1;
    int b = 2;
    int c = 3;
    printf("Address of a: %p\n", (void*)&a);
    printf("Address of b: %p\n", (void*)&b);
    printf("Address of c: %p\n", (void*)&c);
}

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

编译并运行这段代码,你可能会看到如下输出(地址因系统和编译器不同而不同):

Address of a: 0x7ffd5fbff71c
Address of b: 0x7ffd5fbff718
Address of c: 0x7ffd5fbff714

在这个例子中,变量 abc 的地址是相邻的,且按照声明的顺序依次递减(假设栈向下增长)。这说明编译器按照变量声明的顺序分配了内存。

内存对齐

https://blog.csdn.net/weixin_48896613/article/details/127371045

一、为什么要内存对齐

内存对齐是为了提高计算机读取数据的效率。对齐地址通常是2、4、8的倍数。

简单来说,就是方便计算机去读写数据。

    对齐的地址一般都是 n(n = 2、4、8)的倍数。

    (1). 1 个字节的变量,例如 char 类型的变量,放在任意地址的位置上;

    (2). 2 个字节的变量,例如 short 类型的变量,放在 2 的整数倍的地址上;

    (3). 4 个字节的变量,例如 float、int 类型的变量,放在 4 的整数倍地址上;

    (4). 8 个字节的变量,例如 long long、double 类型的变量,放在 8 的整数倍地址上;

二、基本变量类型所占大小

不同系统下基本变量类型占用字节数不同,如char为1字节,int为4字节,指针在32位系统下为4字节,在64位系统下为8字节等。

三、影响内存对齐的情况

1、变量排列顺序。

2、attribute((packed)):取消变量对齐,按照实际占用字节数对齐(就是让变量之间排列紧密,不留缝隙)。(gcc才支持)

3、#pragma pack (n):让变量强制按照 n 的倍数进行对齐,并会影响到结构体结尾地址的补齐(详见四的通常情况下关于结尾地址补齐的描述)。

四、重要结论

系统默认对齐规则总结:

  • 结构体中间:各结构体的起始地址按类型变量默认规则排列,char 类型除外,通常按 2 的倍数对齐。

  • 结构体最后:根据最大变量类型对齐。例如,int 需按 4 的倍数对齐,double 需按 8 的倍数对齐。

  • 结构体嵌套:子结构体的成员变量起始地址按子结构体中最大变量类型对齐。

  • 数组成员:按一个字节对齐。

  • 联合体成员:按联合体中最大类型的整数倍对齐。