Bootstrap

C语言编程之sizeof()和strlen()区别以及对齐和填充详解

一、sizeof()和strlen()的基本用法和区别

在C语言中,sizeof()strlen()是两个常用于获取数据大小或长度的函数,但它们之间有着本质的区别。下面是对这两个函数的基本用法和区别。

1、sizeof操作符

  1. 功能‌:

    • sizeof是一个编译时操作符,用于获取对象或类型所占的内存大小,以字节为单位。
  2. 用法‌:

    • 可以用于数据类型(如intfloatchar等)或变量、指针、数组等对象。
    • 返回的是size_t类型的值。
  3. 示例‌:

#include <stdio.h>

int main() {
    int a = 10;
    char str[] = "Hello";
    
    // 获取int类型的大小
    printf("Size of int: %zu bytes\n", sizeof(int));
    
    // 获取变量a的大小
    printf("Size of variable a: %zu bytes\n", sizeof(a));
    
    // 获取字符串数组str的大小(包括结尾的空字符)
    printf("Size of string array str: %zu bytes\n", sizeof(str));
    
    // 获取指针的大小
    int *ptr = &a;
    printf("Size of pointer: %zu bytes\n", sizeof(ptr));
    
    return 0;
}

输出可能如下(具体大小可能因编译器和平台而异):

Size of int: 4 bytes
Size of variable a: 4 bytes
Size of string array str: 6 bytes
Size of pointer: 8 bytes

2、strlen函数

  1. 功能‌:

    • strlen是一个运行时函数,用于计算字符串的长度,不包括结尾的空字符。
  2. 用法‌:

    • 函数原型为size_t strlen(const char *str);
    • 参数是指向以空字符结尾的字符串的指针。
  3. 示例‌:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello";
    
    // 计算字符串str的长度
    size_t len = strlen(str);
    printf("Length of string str: %zu\n", len);
    
    return 0;
}

输出为:

Length of string str: 5

3、sizeofstrlen的区别

  1. 类型与对象‌:

    • sizeof可以用于数据类型或对象(变量、指针、数组等),而strlen只能用于以空字符结尾的字符串。
  2. 编译时与运行时‌:

    • sizeof是编译时操作符,其值在编译时确定;而strlen是运行时函数,其值在程序运行时确定。
  3. 内存大小与字符串长度‌:

    • sizeof返回的是对象或类型所占的内存大小(以字节为单位);而strlen返回的是字符串的长度(不包括结尾的空字符)。
  4. 返回值类型‌:

    • sizeof的返回值类型是size_tstrlen的返回值类型也是size_t

二、结构体的对齐和填充详解及示例

1、对齐和填充介绍

sizeof操作符用于获取对象或类型所占的内存大小,以字节为单位。当sizeof用于结构体时,它会计算结构体所有成员所占的内存大小之和,包括成员之间的任何填充(padding)或对齐(alignment)所占的空间。这是因为编译器可能会为了优化内存访问速度或满足特定硬件平台的要求,在结构体成员之间插入填充字节,或者对结构体成员进行对齐。

1、结构体对齐和填充

  1. 对齐‌:结构体成员在内存中的地址通常是其类型的倍数。例如,如果某个成员是int类型(通常占4个字节),则它的地址通常是4的倍数。这种对齐要求可以确保处理器在访问该成员时能够高效地进行。

  2. 填充‌:为了满足对齐要求,编译器可能会在结构体成员之间插入额外的字节(称为填充)。这些填充字节不存储任何有效数据,仅用于确保后续成员的对齐。

2、使用sizeof计算结构体长度

当你使用sizeof操作符来计算结构体的长度时,编译器会考虑结构体的所有成员以及它们之间的填充和对齐。因此,sizeof返回的值通常是结构体实际在内存中占用的字节数,这可能大于结构体成员大小之和。

3、示例代码

以下是一个示例结构体及其sizeof计算的代码:

#include <stdio.h>

// 定义一个结构体
struct MyStruct {
    char a;    // 1字节
    int b;     // 4字节(通常)
    short c;   // 2字节(通常)
    char d;    // 1字节
};

int main() {
    // 计算结构体的大小
    size_t structSize = sizeof(struct MyStruct);
    
    // 打印结构体的大小
    printf("Size of struct MyStruct: %zu bytes\n", structSize);
    
    return 0;
}

在这个例子中,我们定义了一个包含四个成员的结构体MyStruct。尽管这些成员的总大小是1 + 4 + 2 + 1 = 8字节,但由于对齐和填充的原因,实际的结构体大小可能会更大。例如,在某些编译器和平台上,int成员b可能需要对齐到4字节边界,而short成员c可能需要对齐到2字节边界。为了满足这些对齐要求,编译器可能会在char成员aint成员b之间,以及short成员cchar成员d之间插入填充字节。

运行上述代码,你将看到结构体MyStruct的实际大小(以字节为单位)。这个大小可能因编译器、平台或编译选项的不同而有所不同。

2、手工控制结构体的对齐和填充

结构体的对齐和填充通常是由编译器自动处理的,以优化内存访问速度或满足特定硬件平台的要求。然而,有时你可能需要手动控制结构体的对齐和填充,以满足特定的内存布局要求或减少内存占用。

不同的编译器提供了不同的方式来手动控制结构体的对齐和填充。在GCC编译器中,你可以使用__attribute__属性来设置结构体的对齐方式。以下是一些常见的用法和示例代码:

1、使用__attribute__((aligned(N)))控制对齐

__attribute__((aligned(N)))属性可以用于结构体、联合体或类型定义,以指定其对齐方式。N是一个正整数,表示对齐的字节数。该属性会确保对象在内存中的地址是N的倍数。

示例代码:

#include <stdio.h>

// 定义一个对齐到16字节边界的结构体
struct __attribute__((aligned(16))) MyStruct {
    char a;
    int b;
    short c;
};

int main() {
    // 计算并打印结构体的大小和对齐方式
    size_t structSize = sizeof(struct MyStruct);
    size_t structAlign = __alignof__(struct MyStruct);
    
    printf("Size of struct MyStruct: %zu bytes\n", structSize);
    printf("Alignment of struct MyStruct: %zu bytes\n", structAlign);
    
    // 打印结构体实例的地址(应该是16的倍数)
    struct MyStruct *instance = (struct MyStruct*)malloc(sizeof(struct MyStruct));
    printf("Address of struct MyStruct instance: %p\n", (void*)instance);
    free(instance);
    
    return 0;
}

在这个例子中,我们定义了一个对齐到16字节边界的结构体MyStruct。使用__alignof__操作符可以获取结构体的对齐方式。运行程序时,你将看到结构体的大小、对齐方式以及结构体实例的地址(应该是16的倍数)。

2、使用#pragma pack控制填充(非GCC特定,但常用)

虽然#pragma pack不是GCC特有的属性,但它在许多编译器中都是可用的,包括Microsoft Visual C++和GCC(作为扩展)。#pragma pack指令可以更改结构体成员的对齐方式,从而减少填充字节。

示例代码(GCC扩展):

#include <stdio.h>

// 更改对齐方式为1字节(默认是编译器决定的对齐方式)
#pragma pack(push, 1)
struct PackedStruct {
    char a;
    int b;
    short c;
};
#pragma pack(pop)

int main() {
    // 计算并打印结构体的大小
    size_t structSize = sizeof(struct PackedStruct);
    
    printf("Size of struct PackedStruct: %zu bytes\n", structSize);
    
    return 0;
}

在这个例子中,我们使用#pragma pack(push, 1)将当前的对齐方式更改为1字节对齐,并使用#pragma pack(pop)恢复之前的对齐方式。这会导致结构体PackedStruct中的成员之间没有填充字节,从而减小结构体的大小。

3、注意事项

  • 手动控制结构体的对齐和填充可能会影响程序的性能,因为不对齐的访问可能会导致处理器访问速度的下降。
  • 不同的编译器可能有不同的方式来控制对齐和填充,因此你需要查阅特定编译器的文档来了解详细的用法。
  • 在使用#pragma pack等编译器扩展时,需要注意它们的可移植性和兼容性。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;