How does a language expand itself?
我正在学习C++,我刚刚开始学习Qt对GUI程序进行编码的一些能力。我问自己以下问题:
C++是一种C++语言,它以前没有语法,可以通过Windows来编写一个窗口,或者通过网络进行通信(我承认,API是我完全不理解的)。在我看来,这一切都是非常循环的。在这些库中你能想出什么C++指令?
我意识到这个问题对于一个有经验的软件开发人员来说可能是微不足道的,但是我已经研究了几个小时,没有找到任何直接的响应。它已经到了我不能遵循关于qt的教程的地步,因为库的存在对我来说是不可理解的。
计算机就像一个洋葱,它有许多层,从纯硬件的内核到最外层的应用程序层。每个层将自身的部分暴露到下一个外层,以便外层可以使用一些内层功能。
例如,对于Windows,操作系统会为在Windows上运行的应用程序公开所谓的win32 api。qt库使用该api为自己的api提供使用qt的应用程序。您使用qt,qt使用win32,win32使用较低级别的Windows操作系统,等等,直到硬件中有电信号为止。
型
你是对的,一般来说,图书馆不能使任何不可能的事情成为可能。
但是,图书馆不必用C++编写,以供C++程序使用。即使它们是用C++编写的,它们也可以在内部使用其他没有用C++编写的库。所以C++没有提供任何方式来做这件事并不妨碍它被添加,只要有一些方法在C++之外做。
在相当低的水平上,由C++(或C)调用的一些函数将以汇编的形式编写,并且程序集包含在C++中做任何不可能(或不容易)的指令,例如调用系统函数。在那一点上,系统调用可以做你的计算机所能做的任何事情,仅仅是因为没有什么能阻止它。
型
C和C++具有2个属性,允许OP所讨论的所有可扩展性。
百万千克1C和C++可以访问内存百万千克1百万千克1C和C++可以调用汇编代码,而不是C或C++语言中的指令。百万千克1
在内核或基本的非保护模式平台中,串行端口或磁盘驱动器等外围设备以与RAM相同的方式映射到内存映射中。内存是一系列开关,翻转外围设备的开关(如串行端口或磁盘驱动器)可以让外围设备做一些有用的事情。
在保护模式操作系统中,当用户想要从用户空间访问内核时(例如,当写入文件系统或在屏幕上绘制像素时),需要进行系统调用。C没有进行系统调用的指令,但是C可以调用能够触发正确系统调用的汇编程序代码,这就是允许C代码与内核对话的原因。
为了使特定平台的编程更容易,系统调用被包装在更复杂的函数中,这些函数可以在自己的程序中执行一些有用的函数。其中一个可以自由地直接调用系统调用(使用汇编程序),但可能更容易使用平台提供的包装函数之一。
还有一个比系统调用更有用的API级别。以malloc为例。这不仅会调用系统获取大内存块,而且会通过对正在发生的事情进行所有簿记来管理该内存。
Win32API使用一个公共平台小部件集包装一些图形功能。Qt以跨平台的方式包装win32(或x windows)API,使这一点更进一步。
从根本上讲,虽然C编译器将C代码转换为机器代码,而且由于计算机是设计用来使用机器代码的,所以您应该期望C能够完成lions共享或计算机可以做什么。包装器库所做的只是为您做一些繁重的工作,这样您就不必这么做了。
语言(如C++ 11)是书面的规范,通常用英语书写。查看最新的C++ 11草案(或者从ISO供应商那里购买昂贵的最终规格)。
你通常使用具有某种语言实现的计算机(原则上,你可以在没有任何计算机的情况下运行C++程序,例如使用一堆人类奴隶来解释它;这将是不道德的和低效的)。
您的C++实现一般在某些操作系统之上工作,并与它通信(使用一些实现特定的代码,通常在某些系统库中)。一般来说,通信是通过系统调用完成的。例如,在syscalls(2)中查找Linux内核上可用的系统调用列表。
从应用程序的角度来看,SysCall是一种基本的机器指令,比如x86-64上的
在我的Linux桌面上,qt库位于通过x Windows协议与x11服务器xorg通信的x11客户机库之上。
在Linux上,在可执行文件上使用
型
我认为你缺少的概念是系统调用。每个操作系统都提供了大量的资源和功能,您可以利用这些资源和功能来完成与操作系统相关的底层工作。即使调用常规库函数,它也可能在后台进行系统调用。
系统调用是一种利用操作系统功能的低级方法,但使用起来可能很复杂,也很麻烦,因此通常"包装"在API中,这样就不必直接处理它们。但在底层,任何涉及O/S相关资源的操作都将使用系统调用,包括打印、联网和套接字等。
在Windows的情况下,微软Windows实际上把它的图形用户界面写入了内核,因此有一些系统调用来生成窗口、绘制图形等。在其他操作系统中,图形用户界面可能不是内核的一部分,在这种情况下,据我所知,不会有任何系统调用与图形用户界面相关的东西,而你只能在电动汽车上工作。在较低的水平,无论低水平的图形和输入相关的调用是可用的。
问得好。每一个新的C或C++开发人员都在考虑这一点。我假设这篇文章的其余部分使用标准的x86机器。如果你正在使用微软C++编译器,打开你的记事本并键入这个文件(命名为文件test .c)
1 2 3 4 | int main(int argc, char **argv) { return 0 } |
现在编译这个文件(使用开发人员命令提示符)cl test.c/fatest.asm
现在打开记事本中的test.asm。你看到的是翻译代码——C/C++被翻译成汇编程序。你明白吗?
1 2 3 4 5 6 7 | _main PROC push ebp mov ebp, esp xor eax, eax pop ebp ret 0 _main ENDP |
C/C++程序设计为在金属上运行。这意味着他们可以访问较低级别的硬件,从而更容易利用硬件的功能。比如,我要在x86机器上编写一个C库getch()。
根据汇编程序的不同,我会用这种方式键入:
1 2 3 4 5 | _getch proc xor AH, AH int 16h ;AL contains the keycode (AX is already there - so just return) ret |
我用汇编程序运行它并生成一个.obj-name getch.obj。
然后我写一个C程序(我不包括任何内容)
1 2 3 4 5 6 | extern char getch(); void main(int, char **) { getch(); } |
现在命名这个文件-getchtest.c。通过传递getch.obj编译这个文件。(或单独编译到.obj并将getchtest.obj和getch.obj链接在一起以生成getchtest.exe)。
运行getchtest.exe,您会发现它等待键盘输入。
C/C++编程不仅仅是语言。要成为一个优秀的C/C++程序员,你需要对它运行的机器类型有一个很好的理解。您需要知道内存管理是如何处理的,寄存器是如何结构的,等等,您可能不需要所有这些信息来进行常规编程——但是它们将极大地帮助您。除了基本的硬件知识外,如果您了解编译器的工作原理(即它是如何翻译的),它当然会有所帮助,这可以使您根据需要调整代码。这是一个有趣的包裹!
两种语言都支持uuasm关键字,这意味着您也可以混合汇编语言代码。学习C和C++将使你成为一个更全面的程序员。
不必总是与汇编程序链接。我提到这件事是因为我认为这会帮助你更好地理解。大多数这样的库调用使用操作系统提供的系统调用/API(操作系统反过来做硬件交互工作)。
型
How does C++ ... suddenly get such capabilities through libraries
written in C++ themselves ?
号
使用其他库没有什么神奇的。库是您可以调用的简单的大功能包。
考虑一下你在写这样的函数
1 2 3 4 | void addExclamation(std::string &str) { str.push_back('!'); } |
现在,如果包含该文件,则可以编写
因此,为了回答你的问题,C++如何能够通过C++编写的库来绘制窗口,答案是相同的。其他人编写了函数来完成这项工作,然后将它们编译并以库的形式提供给您。
其他的问题回答了窗口绘图实际上是如何工作的,但是你听起来对图书馆的工作方式很困惑,所以我想解决你问题中最基本的部分。
关键是操作系统有可能公开一个API,并详细描述如何使用这个API。
操作系统提供了一组具有调用约定的API。调用约定定义了向API提供参数的方式、返回结果的方式以及如何执行实际调用。
操作系统和为它们创建代码的编译器可以很好地协同工作,因此您通常不必考虑它,只需使用它。
型
为了给其他答案提供稍微不同的观点,我会这样回答。
(免责声明:我只是稍微简化了一些事情,我给出的情况纯粹是假设性的,写下来是为了展示概念,而不是100%真实的生活)。
从另一个角度考虑问题,假设您刚刚编写了一个具有基本线程、窗口和内存管理功能的简单操作系统。你想实现一个C++库,让用户在C++中编程,做一些事情,比如制作Windows,绘制到Windows等等。问题是,如何做到这一点。
首先,由于C++编译成机器代码,所以需要定义一种使用机器代码来与C++接口的方法。这就是函数出现的地方,函数接受参数并给出返回值,因此它们提供了在不同代码段之间传输数据的标准方法。他们通过建立所谓的呼叫约定来做到这一点。
调用约定规定了参数应该放在内存中的位置和方式,以便函数在执行时可以找到它们。当一个函数被调用时,调用函数将参数放在内存中,然后请求CPU跳到另一个函数,在该函数执行它所做的操作后,再跳回到调用它的位置。这意味着被调用的代码绝对可以是任何东西,它不会改变函数的调用方式。然而,在这种情况下,函数背后的代码将与操作系统相关,并将在操作系统的内部状态下运行。
所以,很多个月后,你已经整理好了所有的操作系统功能。您的用户可以调用函数来创建窗口并绘制它们,它们可以生成线程和各种奇妙的东西。但问题是,您的操作系统的功能将不同于Linux的功能或Windows的功能。因此,您决定需要为用户提供一个标准界面,这样他们就可以编写可移植代码。这就是qt的由来。
正如您几乎可以肯定的那样,qt有很多有用的类和函数,用于执行操作系统所做的各种操作,但其方式似乎与底层操作系统无关。这样做的方式是qt提供的类和函数在用户看来是统一的,但是函数背后的代码对于每个操作系统都是不同的。例如,qt的qapplication::closeAllWindows()实际上会根据所使用的版本调用每个操作系统的专用窗口关闭函数。在Windows中,它很可能调用closewindow(hwnd),而在使用x window系统的操作系统中,它可能调用xdestroywindow(display,window)。
显而易见,一个操作系统有许多层,所有层都必须通过许多种类的接口进行交互。有很多方面我都没有提到,但要解释它们需要很长时间。如果您对操作系统的内部工作更感兴趣,我建议您查看OS dev wiki。
请记住,许多操作系统选择将接口暴露给C/C++的原因是它们编译成机器代码,它们允许汇编指令与它们自己的代码混合,它们为程序员提供了很大的自由度。
这里又发生了很多事情。我想继续解释库是怎样的。SO和.DLL文件不必用C/C++编写,可以用汇编语言或其他语言编写,但是我觉得如果我再加一点,我也可以写一篇完整的文章,而且我很想做,我没有一个站点来主持它。
首先,我觉得有点误解
How does C++, which previously had no syntax capable of asking the OS for a window or a way to communicate through networks
没有执行操作系统操作的语法。这是语义学的问题。
suddenly get such capabilities through libraries written in C++ themselves
嗯,操作系统主要是用C编写的。您可以使用共享库(so,dll)来调用外部代码。此外,操作系统代码可以在syscalls*上注册系统例程,也可以使用assembly调用中断。共享库通常只是让系统调用您,所以您不用使用内联程序集。
这是一个不错的教程:http://www.win.tue.nl/~aeb/linux/lk/lk-4.html它适用于Linux,但原理是相同的。
操作系统如何操作图形卡、网卡等?这是一个非常广泛的主题,但大多数情况下你需要访问中断,端口或写一些数据到特殊的内存区域。由于这些操作是受保护的,您无论如何都需要通过操作系统调用它们。
创建窗口不需要特殊的语法。所需要的只是操作系统提供了一个创建窗口的API。这样的API由简单的函数调用组成,其中C++提供了语法。
此外,C和C++被称为系统编程语言,并且能够访问任意指针(可以通过硬件映射到某些设备)。此外,调用在assembly中定义的函数也相当简单,这允许处理器提供完整的操作范围。因此,有可能使用C或C++编写一个操作系统本身以及少量的程序集。
还应该提到QT是一个坏例子,因为它使用一个所谓的元编译器来扩展C++的语法。但是,这与它调用操作系统提供的API来实际绘制或创建窗口的能力无关。
型
当你试图在屏幕上绘制一些东西时,你的代码会调用另一段代码来调用其他代码(等等),直到最后出现一个"系统调用",这是一条CPU可以执行的特殊指令。这些指令可以用汇编语言编写,也可以用C++编写,如果编译器支持他们的"本质"(这是编译器处理的函数),特别是"通过将它们转换成CPU能够理解的特殊代码"。他们的工作是告诉操作系统做些什么。
当系统调用发生时,调用另一个函数(等)的函数,直到最后命令显示驱动程序在屏幕上绘制某些内容。此时,显示驱动程序查看物理内存中的一个特定区域,该区域实际上不是内存,而是一个可以写入的地址范围,就好像它是内存一样。然而,写入该地址范围会导致图形硬件截获内存写入,并在屏幕上绘制一些内容。编写这个内存区域是可以在C++中编码的,因为在软件方面,它只是一个普通的内存访问。只是硬件处理方式不同而已。所以这是一个关于它如何工作的基本解释。
型
你的C++程序使用的是Qt库(也用C++编码)。qt库将使用Windows CreateWindowEx函数(在kernel32.dll中用C编码)。或者在Linux下,它可能使用XLIB(也用C编码),但它也可能发送原始字节,在X协议中,这意味着"请为我创建一个窗口"。
与您的第二十二条问题有关的是历史注释:"第一个C++编译器是用C++编写的",虽然实际上它是一个C编译器,有几个C++概念,足以编译第一个版本,然后可以编译自己。
类似地,gcc编译器使用gcc扩展:它首先被编译成一个版本,然后用于重新编译自己。(GCC建造说明)
我怎么看这个问题,这实际上是一个编译器问题。
以这种方式来看,你在程序集中编写了一段代码(你可以用任何语言编写),它将你最新编写的要调用z++的语言翻译成程序集,为了简单起见,我们称它为编译器(它是一个编译器)。
现在你给这个编译器一些基本的函数,这样你就可以写int,string,array等等。实际上你给它足够的能力,这样你就可以用z++来写编译器了。现在有了一个用z++编写的z++编译器,非常好。
更酷的是,现在您可以使用编译器已有的功能向其添加功能,从而通过使用以前的功能扩展具有新功能的z++语言。
例如,如果您编写的代码足以绘制任何颜色的像素,那么您可以使用z++将其展开,以绘制所需的任何内容。
硬件就是实现这一点的原因。您可以将图形内存视为一个大数组(由屏幕上的每个像素组成)。要绘制到屏幕上,您可以使用C++或任何允许直接访问该内存的语言来写入该内存。该存储器恰好可以通过图形卡访问或位于图形卡上。
在现代系统中,由于各种限制,直接访问图形内存需要编写驱动程序,因此使用间接方法。库创建一个窗口(实际上只是一个图像,像其他图像一样),然后将该图像写入图形内存,然后GPU将其显示在屏幕上。除了能够写入特定的内存位置之外,没有什么可以添加到语言中,这就是指针的作用。