语言如何扩展? [关闭]

问题描述 投票:208回答:15

我正在学习C ++,我刚开始学习Qt编写GUI程序的一些功能。我问自己以下问题:

以前没有语法能够通过网络询问操作系统窗口或通过网络进行通信的方法(我承认,我还不完全理解的API)C ++如何突然通过用C ++编写的库本身获得这样的功能?这对我来说似乎非常圆润。您可以在这些库中提供哪些C ++指令?

我意识到这个问题对于经验丰富的软件开发人员来说似乎微不足道,但我一直在研究几个小时而没有找到任何直接的反应。它已经达到了我无法遵循关于Qt的教程的程度,因为库的存在对我来说是不可理解的。

c++ libraries bootstrapping
15个回答
195
投票

计算机就像一个洋葱,它有许多层,从纯硬件的内核到最外层的应用层。每个层将其自身的一部分暴露给下一个外层,以便外层可以使用一些内层功能。

例如,在例如Windows操作系统为在Windows上运行的应用程序公开了所谓的WIN32 API。 Qt库使用该API为使用Qt的应用程序提供自己的API。你使用Qt,Qt使用WIN32,WIN32使用较低级别的Windows操作系统,依此类推,直到硬件中出现电子信号。


7
投票

首先,我认为有一点误会

C ++如何以前没有语法能够向操作系统询问窗口或通过网络进行通信的方式

执行OS操作没有语法。这是语义问题。

突然通过用C ++编写的库来获得这样的功能

好吧,操作系统主要在C中编写。您可以使用共享库(因此,dll)来调用外部代码。此外,操作系统代码可以在syscalls *或中断上注册系统例程,您可以使用程序集调用这些例程。共享库通常只是让系统为您调用,因此您可以使用内联汇编。

这是关于它的好教程:http://www.win.tue.nl/~aeb/linux/lk/lk-4.html 这是针对Linux的,但原则是一样的。

操作系统如何对图形卡,网卡等进行操作?这是一个非常广泛的主题,但大多数情况下你需要访问中断,端口或将一些数据写入特殊的内存区域。由于这些操作受到保护,因此无论如何都需要通过操作系统调用它们。


7
投票

为了尝试对其他答案提供略微不同的观点,我将这样回答。

(免责声明:我稍微简化了一些事情,我给出的情况纯粹是假设的,而且是作为一种展示概念的手段而不是100%真实的生活)。

从另一个角度思考问题,假设您刚刚编写了一个具有基本线程,窗口和内存管理功能的简单操作系统。你想要实现一个C ++库,让用户在C ++中编程,并做一些事情,如制作窗口,绘制到窗口等。问题是,如何做到这一点。

首先,由于C ++编译为机器代码,您需要定义一种使用机器代码与C ++接口的方法。这是函数的来源,函数接受参数并给出返回值,因此它们提供了在不同代码段之间传输数据的标准方法。他们通过建立称为调用约定的东西来做到这一点。

一个调用约定说明了参数应该放在内存中的位置和方式,以便函数在执行时可以找到它们。当一个函数被调用时,调用函数将参数放在内存中,然后要求CPU跳转到另一个函数,在跳转回调用函数之前它会执行它所执行的操作。这意味着被调用的代码绝对是任何东西,它不会改变函数的调用方式。但是,在这种情况下,函数背后的代码将与操作系统相关,并将在操作系统的内部状态下运行。

所以,几个月后,你已经整理了所有的OS功能。您的用户可以调用函数来创建窗口并绘制它们,它们可以创建线程和各种奇妙的东西。这是问题所在,你的操作系统的功能将与Linux的功能或Windows的功能不同。因此,您决定需要为用户提供标准接口,以便他们可以编写可移植代码。这是QT的用武之地。

正如您几乎可以肯定的那样,QT拥有大量有用的类和函数来执行操作系统所做的各种事情,但其方式与底层操作系统无关。这种方式的工作方式是QT提供的类和函数在用户看来是一致的,但函数背后的代码对于每个操作系统都是不同的。例如,QT的QApplication :: closeAllWindows()实际上将根据使用的版本调用每个操作系统的专用窗口关闭功能。在Windows中,它很可能会调用CloseWindow(hwnd),而在使用X Window系统的操作系统上,它可能会调用XDestroyWindow(显示,窗口)。

很明显,操作系统有许多层,所有层都必须通过多种类型的接口进行交互。有许多方面我甚至没有涉及,但解释它们都需要很长时间。如果您对操作系统的内部工作方式更感兴趣,我建议您查看OS dev wiki

请记住,许多操作系统选择向C / C ++公开接口的原因是它们编译为机器代码,它们允许汇编指令与自己的代码混合在一起,它们为程序员提供了很大的自由度。

同样,这里有很多事情要做。我想继续解释像.so和.dll文件这样的库不必用C / C ++编写,可以用汇编语言或其他语言编写,但我觉得如果我再添加,我也可以写一篇完整的文章,尽管我很乐意这么做,但我没有网站来托管它。


6
投票

当您尝试在屏幕上绘制某些内容时,您的代码会调用其他一些代码来调用其他代码(等等),直到最后出现“系统调用”,这是CPU可以执行的特殊指令。这些指令可以用汇编语言编写,也可以用C ++编写,如果编译器支持它们的“内在函数”(编译器通过将它们转换成CPU可以理解的特殊代码来“专门”处理的函数)。他们的工作是告诉操作系统做些什么。

当系统调用发生时,调用一个调用另一个函数(等)的函数,直到最后显示驱动程序被告知在屏幕上绘制一些东西。此时,显示驱动程序查看物理内存中实际上不是内存的特定区域,而是查看可写入的地址范围,就好像它是内存一样。但是,写入该地址范围会导致图形硬件拦截内存写入,并在屏幕上绘制内容。 写入这个内存区域可以用C ++编写,因为在软件方面它只是一个常规的内存访问。只是硬件处理它的方式不同。 所以这是它如何运作的真正基本解释。


4
投票

您的C ++程序使用Qt库(也用C ++编写)。 Qt库将使用Windows CreateWindowEx函数(在kernel32.dll中的C编码)。或者在Linux下它可能使用Xlib(也用C编码),但它也可以发送X协议中的原始字节“请为我创建一个窗口”。

与你的catch-22问题相关的是历史记录“第一个C ++编译器是用C ++编写的”,尽管实际上它是一个带有一些C ++概念的C编译器,足以编译第一个版本,然后可以编译自己。

类似地,GCC编译器使用GCC扩展:它首先被编译为一个版本,然后用于重新编译自己。 (GCC build instructions)


2
投票

我怎么看问题这实际上是一个编译器问题。

这样看,你在Assembly中编写了一段代码(你可以用任何语言编写),它将你想要调用Z ++的新编写的语言翻译成Assembly,为简单起见,我们称之为编译器(它是编译器) 。

现在你给这个编译器一些基本的函数,这样你就可以编写int,string,arrays等。实际上你给它足够的能力,这样你就可以用Z ++编写编译器了。现在你有一个用Z ++编写的Z ++编译器,非常整洁。

更酷的是,现在你可以使用它已有的功能为该编译器添加功能,从而通过使用以前的功能扩展Z ++语言的新功能

例如,如果您编写足够的代码来绘制任何颜色的像素,那么您可以使用Z ++扩展它以绘制您想要的任何颜色。


0
投票

硬件是允许这种情况发生的原因。您可以将图形内存视为一个大型数组(由屏幕上的每个像素组成)。要绘制到屏幕,您可以使用C ++或任何允许直接访问该内存的语言写入此内存。该内存恰好可以通过显卡访问或位于显卡上。

在现代系统上直接访问图形内存需要编写驱动程序,因为有各种限制因此您使用间接方法。创建窗口的库(实际上只是像任何其他图像一样的图像),然后将该图像写入GPU然后在屏幕上显示的图形内存。除了能够写入特定的内存位置之外,没有什么必须添加到语言中,这是指针的用途。


60
投票

你是对的,一般来说,图书馆不可能做出任何可能的事情。

但是库不必用C ++编写,以便可以被C ++程序使用。即使它们是用C ++编写的,它们也可能在内部使用其他不是用C ++编写的库。因此,只要有一些方法可以在C ++之外进行,C ++没有提供任何方法来实现它并不会阻止它被添加。

在一个相当低的层次上,C ++(或C)调用的一些函数将用汇编语言编写,汇编包含所需的指令,以便在C ++中做任何不可能(或不容易)的事情,例如调用系统功能。此时,系统调用可以执行您的计算机能够执行的任何操作,只是因为没有什么能阻止它。


43
投票

C和C ++有2个属性,允许OP所讨论的所有这些可扩展性。

  1. C和C ++可以访问内存
  2. C和C ++可以为不使用C或C ++语言的指令调用汇编代码。

在内核或基本的非保护模式平台中,串行端口或磁盘驱动器等外设以与RAM相同的方式映射到内存映射中。内存是一系列交换机,翻转外围设备的交换机(如串口或磁盘驱动程序)可以让外围设备做有用的事情。

在受保护模式的操作系统中,当想要从用户空间访问内核时(例如,在写入文件系统或在屏幕上绘制像素时),需要进行系统调用。 C没有进行系统调用的指令,但是C可以调用汇编程序代码来触发正确的系统调用,这就是允许一个人的C代码与内核通信的原因。

为了使特定平台的编程更容易,系统调用包含在更复杂的函数中,这些函数可以在一个人自己的程序中执行一些有用的功能。可以直接调用系统调用(使用汇编程序),但可能更容易使用平台提供的其中一个包装函数。

还有另一个级别的API比系统调用更有用。以malloc为例。这不仅会调用系统来获取大块内存,而且会通过对所发生的事情进行所有记录来管理这个内存。

Win32 API使用通用平台窗口小部件集包装一些图形功能。 Qt通过以跨平台方式包装Win32(或X Windows)API来进一步解决这个问题。

从根本上说,虽然C编译器将C代码转换为机器代码,并且由于计算机设计为使用机器代码,因此您应该期望C能够完成狮子会共享或计算机可以执行的操作。包装程序库所做的就是为您完成繁重的任务,以便您不必这样做。


23
投票

语言(如C++11)是纸上的规格,通常用英文书写。查看最新的C++11 draft(或从ISO供应商处购买昂贵的final spec)。

您通常使用具有某种语言实现的计算机(原则上您可以在没有任何计算机的情况下运行C ++程序,例如使用一堆解释它的人类奴隶;这将是不道德和低效的)

您的C ++实现通用工作在某些操作系统之上并与之通信(使用某些特定于实现的代码,通常在某些系统库中)。一般来说,沟通是通过system calls完成的。查看syscalls(2)中的实例,以获取Linux kernel上可用的系统调用列表。

从应用程序的角度来看,系统调用是基本的机器指令,如x86-64上的SYSENTER,有一些约定(ABI

在我的Linux桌面上,Qt库位于X11客户端库之上,与x11服务器Xorg通过X Windows protocols进行通信。

在Linux上,在可执行文件上使用ldd来查看库的依赖项(长)列表。在运行的进程中使用pmap来查看哪些是在运行时“加载”的。顺便说一句,在Linux上,你的应用程序可能只使用免费软件,你可以研究它的源代码(从Qt到Xlib,libc,......内核)来了解更多正在发生的事情


19
投票

我认为你缺少的概念是system calls。每个操作系统都提供了大量的资源和功能,您可以利用这些资源和功能来执行与操作系统相关的低级操作。即使您调用常规库函数,它也可能在幕后进行系统调用。

系统调用是利用操作系统强大功能的低级方法,但使用起来既复杂又麻烦,因此通常会“包装”在API中,这样您就不必直接处理它们。但在下面,几乎所有涉及O / S相关资源的操作都将使用系统调用,包括打印,网络和套接字等。

在Windows的情况下,Microsoft Windows将其GUI实际写入内核,因此存在用于制作窗口,绘制图形等的系统调用。在其他操作系统中,GUI可能不是内核的一部分,在这种情况下据我所知,没有任何系统调用GUI相关的东西,你只能在更低级别工作,无论任何低级图形和输入相关的调用是可用的。


15
投票

好问题。每个新的C或C ++开发人员都会考虑到这一点。我正在为这篇文章的其余部分假设一个标准的x86机器。如果您使用的是Microsoft C ++编译器,请打开记事本并输入(将文件命名为Test.c)

int main(int argc, char **argv)
{
   return 0
}

现在编译此文件(使用开发人员命令提示符)cl Test.c /FaTest.asm

现在在你的记事本中打开Test.asm。你看到的是翻译的代码 - C / C ++被翻译成汇编程序。你有提示吗?

_main   PROC
    push    ebp
    mov ebp, esp
    xor eax, eax
    pop ebp
    ret 0
_main   ENDP

C / C ++程序设计用于在金属上运行。这意味着他们可以访问更低级别的硬件,从而更容易利用硬件的功能。说,我打算在x86机器上写一个C库getch()。

根据汇编程序,我会这样输入:

_getch proc 
   xor AH, AH
   int 16h
   ;AL contains the keycode (AX is already there - so just return)
ret

我用汇编程序运行它并生成一个.OBJ - 将它命名为getch.obj。

然后我写了一个C程序(我不#include任何东西)

extern char getch();

void main(int, char **)
{
  getch();
}

现在命名这个文件 - GetChTest.c。通过传递getch.obj来编译此文件。 (或者单独编译为.obj和LINK GetChTest.Obj和getch.Obj一起生成GetChTest.exe)。

运行GetChTest.exe,您会发现它等待键盘输入。

C / C ++编程不仅仅与语言有关。要成为一名优秀的C / C ++程序员,您需要对其运行的机器类型有一个很好的理解。您将需要知道如何处理内存管理,如何构建寄存器等等。您可能不需要所有这些信息用于常规编程 - 但它们会极大地帮助您。除了基本的硬件知识之外,如果您了解编译器的工作原理(即,它是如何翻译的),它肯定会有所帮助 - 这可以让您根据需要调整代码。这是一个有趣的包!

两种语言都支持__asm关键字,这意味着您也可以混合使用汇编语言代码。学习C和C ++将使你成为一个更好的整体程序员。

没有必要始终与Assembler链接。我曾经提到它,因为我认为这会帮助你更好地理解。大多数情况下,大多数此类库调用都使用操作系统提供的系统调用/ API(操作系统依次执行硬件交互操作)。


10
投票

C ++如何通过用C ++编写的库本身突然获得这样的功能?

使用其他库没有什么神奇之处。图书馆是你可以打电话的简单大包。

考虑自己编写这样的函数

void addExclamation(std::string &str)
{
    str.push_back('!');
}

现在,如果你包含该文件,你可以写addExclamation(myVeryOwnString);。现在您可能会问,“C ++是如何突然获得向字符串添加感叹号的功能的?”答案很简单:你写了一个函数,然后你调用它。

因此,要回答关于C ++如何通过C ++编写的库来获取窗口的能力的问题,答案是一样的。其他人写了一些函数来编写它们,然后编译它们并以库的形式提供给你。

其他问题回答了窗口绘图的实际效果,但你对图书馆的运作方式感到困惑,所以我想解决你问题的最基本部分。


8
投票

关键是操作系统可能会公开API以及有关如何使用此API的详细说明。

操作系统提供了一组带有调用约定的API。调用约定是定义参数给API的方式以及返回结果以及如何执行实际调用。

操作系统和为它们创建代码的编译器可以很好地协同工作,因此您通常不必考虑它,只需使用它。


7
投票

创建窗口不需要特殊的语法。所需要的只是操作系统提供了一个API来创建窗口。这样的API由简单的函数调用组成,C ++确实为其提供了语法。

此外,C和C ++是所谓的系统编程语言,能够访问任意指针(可能由硬件映射到某些设备)。此外,调用程序集中定义的函数也非常简单,它允许处理器提供的全部操作。因此,可以使用C或C ++以及少量的汇编来编写OS本身。

还应该提到Qt是一个不好的例子,因为它使用所谓的元编译器来扩展C ++的语法。然而,这与它调用操作系统提供的API以实际绘制或创建窗口的能力无关。

© www.soinside.com 2019 - 2024. All rights reserved.