C/C++编译器中有趣的设计

113 阅读3分钟

深入 C/C++ 过程中,比如了解类的内存布局后,回头看看那些曾经视作理所当然的语法,总会冒出些疑问。为啥这些语法能正确执行,就像指针保存的只是一个地址,并没有记录再多信息,那么C/C++是怎么能正确释放内存空间,诸如此类等等。

free 函数如何知道要释放的内存大小?

动态申请内存时会包括存放 mem_control_block 结构体的内存和存放 10 个 Fish 对象的内存,其中 mem_control_block 结构体会记录申请的内存大小,然后 calloc 函数把指向 10 个 Fish 对象的地址返回去,这样在调用 free 释放空间时通过偏移指针就能知道要释放的内存大小。

#include <iostream>

class Fish
{
public:
    int m_attr1;
};

int main()
{
    Fish *pFish = (Fish*)calloc(10, sizeof(Fish));
    free(pFish);

    return 0;
}

image.png

为何重复包含头文件在编译时可能引发重定义错误?

编译器在预编译阶段将展开 #include 指令,把 xxxx 文件的全部内容插入此处。如此重复包含时可能造成同个文件中多次声明或定义相同的标识符,此时需要借助 #ifndef 指令避免重定义错误。

print_text.c

void print_text(char* text)
{
  //
}

print_line.c

#include "print_text.c"

void print_line(char* text)
{
  print_text(text);
  print_text("\n");
}

main.c

#include "print_text.c"
#include "print_line.c"

void main()
{
  print_text("Hello, world!\n");
  print_line("Hi, world!");
}

依次执行预编译指令 gcc -E print_text.c -o print_text.i、gcc -E print_line.c -o print_line.i、 gcc -E main.c -o main.i 对源文件做预编译处理后获取下述几个文件。

print_text.i

# 0 "print_text.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "print_text.c"

void print_text(char* text)
{

}

print_line.i

# 0 "print_line.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "print_line.c"
# 1 "print_text.c" 1

void print_text(char* text)
{

}
# 2 "print_line.c" 2

void print_line(char* text)
{
  print_text(text);
  print_text("\n");
}

main.c

# 0 "main.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "main.c"

# 1 "print_text.c" 1

void print_text(char* text)
{

}
# 3 "main.c" 2
# 1 "print_line.c" 1
# 1 "print_text.c" 1

void print_text(char* text)
{

}
# 2 "print_line.c" 2

void print_line(char* text)
{
  print_text(text);
  print_text("\n");
}
# 4 "main.c" 2

void main()
{
  print_text("Hello, world!\n");
  print_line("Hi, world!");
}

new[] 为何要搭配 delete[] 使用?

delete 和 delete[] 都能释放内存,只是 delete 只会调用第一个对象的析构函数,delete[] 会调用每个对象的析构函数。

#include <iostream>

class Fish
{
public:
    std::string m_name;

    Fish(std::string name) : m_name(name) {}

    ~Fish()
    {
        std::cout << "Fish '" << m_name << "' ~Fish() called" << std::endl;
    };
};

int main()
{
    {
        Fish* pFish = new Fish[3] {{"fish-A1"}, {"fish-A2"}, {"fish-A3"}};

        delete[] pFish;
    }

    {
        Fish* pFish = new Fish[3] {{"fish-B1"}, {"fish-B2"}, {"fish-B3"}};

        delete pFish;
    }

    return 0;
}

输出结果:
Fish 'fish-A3' ~Fish() called
Fish 'fish-A2' ~Fish() called
Fish 'fish-A1' ~Fish() called
Fish 'fish-B1' ~Fish() called

通过对象指针调用虚函数时,C++是如何确定是哪个类的函数成员?

有虚函数的类对象中会有指针指向虚函数表。

#include <iostream>

class Fish
{
public:
    int m_attr1;
    Fish() : m_attr1(0) {}

    virtual void swimming()
    {
        std::cout << "Fish swimming..." << std::endl;
    }
};

class Food
{
public:
    int m_attr1;
    Food() : m_attr1(0) {}

};

class FishFood : public Fish, public Food
{
public:
    int m_attr1;
    FishFood() : m_attr1(0) {}

    void swimming() override
    {
        std::cout << "Food swimming..." << std::endl;
    }
};

int main()
{
    FishFood fishFood;
    Fish *pFish = &fishFood;

    pFish->swimming();

    return 0;
}

输出结果:
Food swimming...

image.png

image.png

通过对象指针访问数据成员时,C++是如何确定是哪个类的数据成员?

赋值给对象指针时,C++会偏移指针的位置到指定类型的对象上。

#include <iostream>

class Fish
{
public:
    int m_attr1;
    void swimming()
    {
        std::cout << "Fish swimming..." << std::endl;
    }
};

class Food
{
public:
    int m_attr1;
};

class FishFood : public Fish, public Food
{
public:
    int m_attr1;
    void swimming()
    {
        std::cout << "Food swimming..." << std::endl;
    }
};

int main()
{
    FishFood fishFood;
    Fish *pFish = &fishFood;
    Food *pFood = &fishFood;
    FishFood *pFishFood = &fishFood;

    std::cout << "Fish*: " << pFish << std::endl;
    std::cout << "Food*: " << pFood << std::endl;
    std::cout << "FishFood*: " << pFishFood << std::endl;

    return 0;
}

输出结果:
Fish*: 0x61fdfc
Food*: 0x61fe00
FishFood*: 0x61fdfc