引擎盖下:C和C ++的大小的重要性
#编程 #c #cpp

在打字的编程语言中,数据类型起着非常重要的作用,因为它们允许程序员编写高效可靠的代码,而不必担心如何将数据类型映射到内存中的基本细节。编译器将为该变量分配适当的内存量。

不同的数据类型可以占用不同数量的内存。以整数为例,此数据类型通常占用4个字节的内存(x86架构中的dword),而浮点数据类型可以占用8个字节(或更多,具体取决于您使用的编译器,在此上提供很大的灵活性是GMP

话虽如此,使用操作员(或功能,取决于您喜欢查看)是很有意义的。想象一下,您想编写一个代码,该代码使用malloc分配了一系列双打。代码看起来像这样:

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

int main(int argc, char* argv) {
    int n = 5;
    double *arr = (double*) malloc(n * sizeof(double));

    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // initialize and print array
    for (int i = 0; i < n; i++) {
        arr[i] = i * 1.0f;
        printf("%lf ", arr[i]);
    }
    printf("\n");

    // free memory to avoid memory leaks
    free(arr);
    return 0;
}

如果我们替换

    double *arr = (double*) malloc(n * sizeof(double));


    # DEFINE DBL_SIZE 8
    // previous code
    double *arr = (double*) malloc(n * DBL_SIZE);

,如果此代码由其他编译器或其他架构编译,这可能会导致问题。虽然这在这个简单的示例中可能并不明显,但下一个示例将有助于证明使用sizeof

的好处

内存管理

sizeof的主要用途之一是内存管理。每当我们在程序中声明一个变量时,编译器都会根据其数据类型为其分配一定数量的内存。 sizeof运算符允许我们确定此内存分配的大小。

例如,假设我们在程序中声明一个名为number的整数变量。我们可以使用sizeof操作员确定字节中此变量的大小:

int number;
printf("Size of number: %d bytes\n", sizeof(number));

这将在大多数现代系统上打印“数字的大小:4个字节”,因为整数通常为4个字节。我们可以使用此信息来确保我们为变量分配适当的内存并优化程序的内存使用情况。

数据操纵

sizeof的另一种重要用法是在数据操作中。使用sizeof运算符可以帮助我们确定数组,结构和其他复杂数据类型的大小。此信息可用于以各种方式操纵这些数据类型。

让我们用示例分解每种类型

数组

例如,假设我们有一个名为numbers的整数。我们可以使用sizeof操作员来确定此数组中的元素数量:

int numbers[10];
int num_elements = sizeof(numbers) / sizeof(numbers[0]);
printf("Number of elements in array: %d\n", num_elements);

这将产生以下输出“数组中的元素数:10”,因为数组中有10个元素。此信息对于我们要在任何数组元素(遍历,排序等)上进行的任何操作都非常有用。

在数组的情况下,事情可能看起来很琐碎,但是,让我们看看以下示例,因为它显示了一个有趣的行为

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

int main() {
    int n = 5;
    double *arr = (double*) malloc(n * sizeof(double));

    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // initialize array and print original array
    printf("Original array: ");
    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * 1.0f;
        printf("%lf ", arr[i]);
    }
    printf("\n");

    // reallocate array using realloc()
    size_t new_n = 2 * (sizeof(arr) / sizeof(arr[0]));
    arr = (double*) realloc(arr, new_n * sizeof(double));

    if (arr == NULL) {
        printf("Memory reallocation failed!\n");
        return 1;
    }

    // print new array
    printf("New array: ");
    for (int i = 0; i < new_n; i++) {
        printf("%lf ", arr[i]);
    }
    printf("\n");

    // free memory
    free(arr);
    return 0;
}

此代码以当前形式的输出为

Original array: 1.000000 2.000000 3.000000 4.000000 5.000000 
New array: 1.000000 2.000000 

最初这似乎很奇怪,但实际上非常简单。 sizeof如果在编译时已知大小并创建动态数组,则可以使用指针(根据所使用的CPU的架构具有不同的尺寸),则指针的大小是固定的,无论数据如何类型。因此,当我们在arr上创建调用sizeof时,它返回8(指针的大小)和arr[0]的大小为8(double),这会产生(2 *(8/8)),这就是2的原因,这就是为什么新数组的原因只有两个元素。

这里的要点是,对于动态阵列,您应该存储数组的大小,因为使用sizeof会产生不正确的结果

结构

同样,我们可以使用sizeof操作员确定字节中的结构大小:

struct person {
  char name[50];
  int age;
  float height;
};
struct person john;
printf("Size of person struct: %d bytes\n", sizeof(john));

这将输出“人的大小结构:60字节”。这看起来很奇怪,不是吗?我们知道该结构包含一个50个字节的char阵列,一个4个字节的整数和4个字节的浮点,这应该累加多达58(50 + 4 + 4),但是它显示了60个字节,为什么这就是为什么?让我们看看工会是否表现出类似的行为

工会

工会是另一种复杂的数据类型,可以从sizeof运算符中受益。工会允许我们将不同的数据类型存储在同一内存位置。我们可以使用sizeof操作员来确定工会及其成员的大小。

例如,假设我们有一个称为my_union的联盟,可以容纳整数或浮点。我们可以使用sizeof操作员来确定工会及其成员的规模:

union person {
    char name[50];
    int age;
    float height;
};
union person p;
printf("Size of person: %d bytes\n", sizeof(p));
printf("Size of name: %d bytes\n", sizeof(p.name));
printf("Size of age: %d bytes\n", sizeof(p.age));
printf("Size of height: %d bytes\n", sizeof(p.height)); 

这将输出

Size of person: 52 bytes
Size of name: 50 bytes
Size of age: 4 bytes
Size of height: 4 bytes

52?看起来比结构的情况陌生。

到底是怎么回事?

在C/C ++中,结构和工会用于将相关数据分组在一起,这就是为什么在内存中对齐数据如何对程序性能产生重大影响的原因。

内存对齐是指在内存中对齐数据的过程,以便可以通过CPU更有效地访问它。这是通过用额外字节填充内存来实现的,以便每个数据元素都与其大小的多个内存地址对齐。

填充可确保CPU有效地访问数据元素,这可以使程序执行更快。填充的缺点是浪费的内存空间,有些人将其呼叫内存开销。

为了减少内存开销,使用了包装的概念,其中涉及通过重新排列数据元素的顺序减少填充量。

当填充数据元素时,C标准还指定了结构或工会的开始地址的对齐要求。标准要求,必须将结构或工会的起始地址与结构或工会中最大成员的边界保持一致。这确保了整个结构或联合在内存中正确对齐。

在人联盟的情况下,最大的数据类型的大小为4个字节。如果我们将50除以4,它将给我们12个字节,即48个字节。如果我们使用联合的name成员,我们还有两个字节,并且由于最大的数据类型为4,然后48 + 4 = 52

结构的情况与联合的情况非常相似,并且由于结构涵盖了所有元素,因此结果将为(48 + 2 + 2(对齐) + 4 + 4),等于60。 P>

考虑一个较简单的结构,该结构包含一个double(通常需要8个字节的内存)和一个INT(通常需要4个字节的内存),然后必须将结构本身对齐在8字节边界上。这意味着结构的起始地址必须是8个倍数。如果结构的起始地址不一致与8个字节,则CPU可能需要执行其他操作来提取数据,这可能会导致程序较慢的程序执行。

除了C标准外,某些处理器可能还具有自己的特定一致性要求,以实现最佳性能。对于C程序员来说,重要的是要注意任何特定于处理器的一致性要求,并在设计和实施其程序时考虑它们。

结论

总而言之,sizeof运算符是C和C ++编程的必不可少的工具。它使我们能够确定变量和数据类型的大小,操纵数据并优化我们的程序。通过有效地使用sizeof运算符,我们可以确保程序有效地使用内存并尽其所能。

在您的代码中有效使用sizeof的一些提示:

  • 在为数据类型分配内存时始终使用sizeof
  • 使用sizeof确定结构或联合的大小。
  • 通过使用sizeof,请避免代码中的硬编码值。

通过遵循以下提示,您可以在C和C ++中编写更高效和可维护的代码。

参考

https://stackoverflow.com/questions/2117486/c-pointer-arithmetic
https://stackoverflow.com/questions/14171117/implementation-of-sizeof-operator
https://www.c-faq.com/struct/align.html
https://en.wikipedia.org/wiki/Sizeof
https://cplusplus.com/forum/beginner/279863/
https://stackoverflow.com/questions/14004704/find-malloc-array-length-in-c
https://stackoverflow.com/questions/891471/union-element-alignment
https://stackoverflow.com/questions/16703211/why-128bit-variables-should-be-aligned-to-16byte-boundary