[C Advanced] 24, #pragma analysis

Summary

1) #pragma is used to instruct the compiler to complete some specific actions# Many of the pointers defined by pragma are compiler specific, so they are not portable between different compilers

  • The preprocessor will ignore the #pragma instructions it does not recognize
  • Different compilers may interpret the same #pragma instruction in different ways
  • General usage: #pragma parameter. Different parameter parameters have different syntax and meaning.

2) #pragma message outputs information to the compilation output window during compilation; Unlike #error and #warning, #pragma message only represents a prompt message, not an error. (the behaviors of vc, bcc and gcc compilers are different)

3) #pragma once is used to ensure that the header file is compiled only once# Pragma once is compiler related and not necessarily supported. (vc and gcc support, bcc does not support)

4)#ifndef...#define...#endif can also be used to prevent header files from being included repeatedly. What's the difference between ENDIF and #pragma once?

  • #ifndef mode is supported by C language and can be used in all compilers; If one If h is included n times, the precompiler will judge whether the header file has been included n times.
  • #pragma once mode is compiler related, which may not be supported by all compilers; The precompiler will only process it once and will not judge it later.

5) What is memory alignment?

  • Different types of data are arranged in memory according to certain rules, not necessarily one by one

6) Why do I need memory alignment?

  • The CPU reads the memory not continuously, but in blocks. The size of the block can only be a power of 2, 1, 2, 4, 8 byte
  • When the data of the read operation is not aligned, it takes two bus cycles to access the memory, so the performance will be greatly reduced
  • Some hardware platforms can only read specific types of data from the specified relative address, otherwise hardware exceptions will occur

7) The default alignment of the compiler is 4-byte alignment, #pragma pack(n) can adjust the default alignment of the compiler (vc and bcc compilers support 8-byte alignment, but gcc does not)

8) Memory alignment rules:

·#pragma analysis

#pragma is used to instruct the compiler to complete some specific actions;
#Many of the pointers defined by pragma are compiler specific, so they are not portable between different compilers

  • The preprocessor will ignore the #pragma instructions it does not recognize
  • Different compilers may interpret the same #pragma instruction in different ways
  • General usage: #pragma parameter. Different parameter parameters have different syntax and meaning.

1,#pragma message

  • The message parameter has a similar implementation in most compilers
  • The message parameter outputs a message to the compile output window at compile time
  • message is used to prompt the version information of the code in conditional compilation

    #include <stdio.h>
    
    #if defined(ANDROID20)
      #pragma message("Compile Android SDK 2.0...")
      #define VERSION "ANDROID 2.0"
    
    #elif defined(ANDROID30)
      #pragma message("Compile Android SDK 3.0...")
      #define VERSION "Android 3.0"
    
    #elif defined(ANDROID40)
      #pragma message("Compile Android SDK 4.0...")
      #define VERSION "Amdroid 4.0"
    
    #else 
      #error Compile version is not provided!
     
    #endif
    
    int main()
    {
      printf("%s\n", VERSION);
    
      return 0;
    }
    Compilation results of different compilers:
      bcc compiler: bcc32 -DANDROID30 test.c
      Compile output: Compile Android SDK 3.0...
    
      vc compiler: cl -DANDROID30 test.c
      Compile output: Compile Android SDK 3.0...
    
      gcc compiler: gcc -DANDROID30 test.c
      Compile output: note: #pragma message: Compile Android SDK 4.0...
    
    Output description of three compilers:#pragma message will output prompt information, but the behavior is similar, and the specific implementation may be different
    Intermediate file for single step compilation: gcc -DANDROID30 -E test.c -o test.i
    # 12 "test.c"
    #pragma message("Compile Android SDK 3.0...")
    # 12 "test.c"
    # 20 "test.c"
    int main()
    {
      printf("%s\n", "Android 3.0");
    
      return 0;
    }

    Note: unlike #error and #warning, #pragma message only represents a compilation message, not a program error.

2,#pragmae once

  • #pragma once is used to ensure that the header file is compiled only once
  • #pragma once is compiler related and not necessarily supported

#ifndef...#define...#endif can also be used to prevent header files from being included repeatedly. What's the difference between ENDIF and #pragma once?

  • #ifndef mode is supported by C language and can be used in all compilers; If one If h is included n times, the precompiler will judge whether the header file has been included n times.
  • #pragma once mode is compiler related, which may not be supported by all compilers; The precompiler will only process it once and will not judge it later.

    // test.h
    #pragma once
    int aa = 1;
    
    // test.c
    #include <stdio.h>
    #include "test.h"
    #include "test.h"
    
    int main()
    {
      return 0;
    }
    Compilation results of different compilers:
      bcc compiler: bcc32 test.c
      Compile output: Variable 'aa' is initialized more than once
    
      vc compiler: cl test.c
      Compile output: successful
    
      gcc compiler: gcc test.c
      Compile output: successful
    
    analysis:#pragma once preprocessing indicator can be recognized by vc and gcc, but bcc can't recognize it and ignores it directly. Then aa will be defined twice, resulting in compilation errors

Since #ifndef will be judged n times, but #pragma once is not supported by all compilers, these two methods can be used simultaneously to improve efficiency:

#ifndef _TEST_H_
#define _TEST_H_

#pragma once

// code

#endif

3. #pragma pack and memory alignment

3.1 what is memory alignment?

  • Different types of data are arranged in memory according to certain rules, not necessarily one by one

3.2 why do I need memory alignment?

  • The CPU reads the memory not continuously, but in blocks. The size of the block can only be a power of 2, 1, 2, 4, 8 byte
  • When the data of the read operation is not aligned, it takes two bus cycles to access the memory, so the performance will be greatly reduced
  • Some hardware platforms can only read specific types of data from the specified relative address, otherwise hardware exceptions will occur

3.3 #pragma pack

The compiler's default alignment is 4-byte alignment
#Pragma pack can adjust the default alignment of the compiler

  • #include <stdio.h>
    
    #pragma pack(1)
    struct Test1
    {
      char c1;
      short s;
      char c2;
      int i;
    };
    #pragma pack()
    
    #pragma pack(1)
    struct Test2
    {
      char c1;
      char c2;
      short s;
      int i;
    };
    #pragma pack()
    
    int main()
    {
      printf("sizeof(Test1) = %d\n", sizeof(struct Test1));
      
      printf("sizeof(Test2) = %d\n", sizeof(struct Test2));   
      
      return 0;
    }

    For the same two struct s, after adjusting the memory alignment, the memory occupied by Test1 changes.

3.3 rules of struct memory alignment:

  • The first member starts at an offset of 0
  • Each member is aligned according to the alignment parameter (the smaller of the type size and the pack parameter)

    • The offset address must be divisible by the alignment parameter (offset+size position of the previous member, offset address / alignment parameter = = 0)
    • The type size of structure type members takes the data member with the largest internal length as its size
  • The total length of struct must be an integral multiple of all alignment parameters
#include <stdio.h>

#pragma pack(8)
struct Test1
{
    short a;
    long b;
};

struct Test2
{
    char c;
    struct Test1 st;
    double d;
};
#pragma pack()

int main()
{
    printf("sizeof(Test1) = %d\n", sizeof(struct Test1));
    
    printf("sizeof(Test2) = %d\n", sizeof(struct Test2));   
    
    return 0;
}
Output under different compilers:
vc:8 And 24
gcc:8 And 20
bcc:8 And 24

Analysis: the output results of vc and bcc are consistent with our algorithm. Why does gcc have different results?
Answer: #pragma defines many compiler directives that are unique and not portable between compilers. The gcc compiler does not support 8-byte alignment, so the gcc compiler directly deletes and ignores' #pragma pack(8) ', and still aligns according to 4 bytes. The result is 20:

struct Test2
{                            pack    offset     memberSize
    char c;                    1        0        1
    struct Test1 st;           4        4        8
    double d;                  4        12       8
}                                                    Total size: 20

This paper is summarized from the C language advanced course by Tang zuolin, teacher of "Ditai Software College".
Please correct any mistakes and omissions.

Keywords: C

Added by foamypup on Fri, 24 Dec 2021 05:50:55 +0200