深入 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;
}
为何重复包含头文件在编译时可能引发重定义错误?
编译器在预编译阶段将展开 #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...
通过对象指针访问数据成员时,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