语言如何扩展?
我正在学习C ++,而且我刚刚开始学习一些Qt编写GUI程序的function。 我问自己以下问题:
如果C ++以前没有语法能够向操作系统请求窗口或者通过networking进行通信的方式(我承认我还没有完全理解的API) ,那么C ++本身通过C ++库突然获得了这样的function呢? 这一切似乎对我来说非常循环。 你可能会在这些库中提供哪些C ++指令?
我意识到这个问题对于一个有经验的软件开发人员来说似乎微不足道,但是我一直在研究几个小时而没有find任何直接的回应。 它已经到了不能跟随关于Qt的教程的地步,因为图书馆的存在对我来说是不可理解的。
计算机就像一个洋葱,它有许多层,从纯硬件的内核到最外层的应用层。 每一层将自身的一部分暴露给下一个外层,以便外层可以使用一些内层function。
在例如Windows的情况下,操作系统暴露了在Windows上运行的应用程序的所谓的WIN32 API。 Qt库使用该API来将使用Qt的应用程序提供给它自己的API。 您使用Qt,Qt使用WIN32,WIN32使用较低级别的Windows操作系统,依此类推,直到硬件中出现电信号。
一般来说,你是对的,图书馆不可能做出任何不可能的事情。
但是这些库不一定要用C ++编写才能被C ++程序使用。 即使它们是用C ++编写的,它们也可能在内部使用其他不是用C ++编写的库。 所以C ++没有提供任何方法来做这件事并不妨碍它被添加,只要有一些方法可以在C ++之外做到这一点。
在相当低的层次上,C ++(或C)所调用的某些函数将被编译成程序集,并且程序集包含所需的指令,以便在C ++中做任何不可能(或不容易)的操作,例如调用一个系统function。 在那个时候,那个系统调用可以完成你的计算机所能做的任何事情 ,只是因为没有什么能够阻止它。
C和C ++有两个属性,允许OP在讨论所有这些扩展性。
- C和C ++可以访问内存
- C和C ++可以调用不是C或C ++语言的指令的汇编代码。
在内核或基本的非保护模式平台中,像串行端口或磁盘驱动器这样的外设以与RAM相同的方式映射到内存映射中。 内存是一系列的开关和翻转外设的开关让你的串口或磁盘驱动器做有用的事情。
在保护模式操作系统中,当需要从用户空间访问内核时(比如写入文件系统或在屏幕上绘制像素)需要进行系统调用。 C没有指令进行系统调用,但是C可以调用可以触发正确的系统调用的汇编代码,允许C代码与内核通信。
为了使编程特定的平台更容易,系统调用被包装在更复杂的函数中,可以在自己的程序中执行一些有用的function。 一个可以直接调用系统调用(使用汇编程序),但使用平台提供的其中一个包装函数可能更容易。
还有另一个级别的API比系统调用更有用。 以malloc为例。 这不仅会使系统获得大量的内存,而且还会通过logging所发生的事情来pipe理这些内存。
Win32 API使用通用的平台小部件集来包装一些graphicsfunction。 Qt通过以跨平台的方式封装Win32(或X Windows)API来进一步提高了这一点。
从根本上说,虽然C编译器会将C代码转换为机器代码,并且由于计算机被devise为使用机器代码,您应该期望C能够完成狮子共享或计算机可以执行的操作。 包装器库所做的一切都是为了让你不必担心。
语言(如C ++ 11 )在纸面上是通常用英文书写的规格 。 查看最新的C ++ 11草案 (或从ISO供应商处购买昂贵的最终规格 )。
你通常使用一台具有某种语言实现的计算机(你原则上可以在没有任何计算机的情况下运行一个C ++程序,例如使用一群人类的奴隶来解释它;这是不道德和低效的)
你的C ++实现一般在一些操作系统上面工作并且与它进行通信(使用一些实现特定的代码,通常在一些系统库中)。 一般来说,通信是通过系统调用完成的。 在Linux内核的系统调用列表中查找syscalls(2)的实例。
从应用的angular度来看,系统调用是一个基本的机器指令,如x86-64上的SYSENTER
,有一些约定( ABI )
在我的Linux桌面上,Qt库位于X11客户端库之上,通过X Windows协议与X11服务器Xorg进行 通信 。
在Linux上,在您的可执行文件上使用ldd
来查看库(依赖关系)的(长)列表。 在正在运行的进程中使用pmap
来查看在运行时“加载”哪些进程。 顺便说一下,在Linux上,您的应用程序可能只使用免费软件,您可以研究其源代码(从Qt到Xlib,libc,…内核),以了解更多的情况
我认为你缺less的概念是系统调用 。 每个操作系统都提供了大量的资源和function,您可以利用这些资源和function来执行与操作系统相关的低级操作。 即使您调用常规库函数,也可能在幕后进行系统调用。
系统调用是利用操作系统function的一种低级方式,但是使用起来可能很复杂和繁琐,所以经常被“包装”在API中,所以你不必直接处理它们。 但是在内部,任何涉及O / S相关资源的事情都会使用系统调用,包括打印,networking和套接字等。
在windows的情况下,Microsoft Windows的GUI实际上是写入内核的,所以有制作窗口,绘制graphics等的系统调用。在其他操作系统中,GUI可能不是内核的一部分,在这种情况下据我所知,不会有任何与GUI有关的系统调用,只有在低级别的graphics和input相关的调用可用的情况下,你才能在更低的级别上工作。
好问题。 每个新的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 ++程序被devise为在金属上运行。 这意味着他们可以访问较低级别的硬件,从而更容易利用硬件的function。 说,我要在x86机器上编写一个C库getch()。
根据汇编程序,我会这样input:
_getch proc xor AH, AH int 16h ;AL contains the keycode (AX is already there - so just return) ret
我用汇编程序运行它并生成.OBJ – 将其命名为getch.obj。
然后我写一个C程序(我不包括任何东西)
extern char getch(); void main(int, char **) { getch(); }
现在命名这个文件 – GetChTest.c。 通过传递getch.obj来编译这个文件。 (或单独编译.obj和LINK GetChTest.Obj和getch.Obj一起生成GetChTest.exe)。
运行GetChTest.exe,你会发现它等待键盘input。
C / C ++编程不仅仅是语言。 要成为一名优秀的C / C ++程序员,您需要对其运行的机器types有一个很好的理解。 你将需要知道内存pipe理是如何处理的,寄存器是如何构成的等等。你可能不需要所有这些信息用于定期的编程 – 但是它们将帮助你很大。 除了基本的硬件知识之外,如果您了解编译器的工作方式(例如,它是如何转换的),它肯定会有所帮助 – 这可以使您能够根据需要调整代码。 这是一个有趣的包!
两种语言都支持__asm关键字,这意味着您也可以混用您的汇编语言代码。 学习C和C ++将使你成为一个更好的整体程序员。
不需要总是与汇编链接。 我曾经提到过,因为我认为这会帮助你更好地理解。 大多数情况下,大多数这样的库调用都使用操作系统提供的系统调用/ API(操作系统反过来做硬件交互的东西)。
C ++如何通过使用C ++编写的库本身突然获得这样的function?
使用其他库没有什么不可思议的。 图书馆是可以调用的简单的大包装function。
考虑自己写这样的function
void addExclamation(std::string &str) { str.push_back('!'); }
现在,如果你包含该文件,你可以写addExclamation(myVeryOwnString);
。 现在你可能会问,“C ++如何突然增加了为string添加惊叹号的能力? 答案很简单:你写了一个函数来做这个,然后你调用它。
因此,要回答关于C ++如何通过用C ++编写的库来绘制窗口的能力的问题,答案是一样的。 其他人写了这样的function,然后编译它们,并以图书馆的forms给你。
其他问题回答了窗口绘图的实际工作方式,但是您对图书馆如何工作感到困惑,所以我想解决您的问题中最基本的部分。
关键是操作系统公开API的可能性以及关于如何使用这个API的详细描述。
操作系统提供了一组调用约定的API。 调用约定是定义一个参数给API的方式,以及如何返回结果以及如何执行实际的调用。
操作系统和为它们创build代码的编译器可以很好地协同工作,所以你通常不用考虑,只是使用它。
创build窗口不需要特殊的语法。 所有需要的是操作系统提供了一个API来创build窗口。 这样的API由C ++提供语法的简单函数调用组成。
此外,C和C ++被称为系统编程语言,能够访问任意指针(可能被硬件映射到某个设备)。 此外,调用在程序集中定义的函数也是相当简单的,这允许处理器提供的全部操作。 因此可以使用C或C ++和less量程序集编写操作系统本身。
还应该提到Qt是个不好的例子,因为它使用所谓的元编译器来扩展 C ++的语法。 然而这与它能够调用OS提供的API来绘制或创build窗口无关。
首先,我想,有一点误会
C ++是如何实现的,C ++之前没有语法能够向操作系统请求窗口或通过networking进行通信
操作系统操作没有语法 。 这是语义问题。
通过使用C ++自己编写的库突然获得这样的function
那么,操作系统主要用C语言编写。你可以使用共享库 (so,dll)来调用外部代码。 此外,操作系统代码可以在系统调用*上注册系统例程,或使用汇编可以调用的中断 。 共享库通常只是让系统调用你,所以你不用使用内联汇编。
这是关于这个好教程: http : //www.win.tue.nl/~aeb/linux/lk/lk-4.html
这是为Linux,但原则是相同的。
操作系统如何在graphics卡,网卡等上进行操作? 这是一个非常广泛的主题,但主要是需要访问中断,端口或将一些数据写入特殊内存区域。 由于该操作受到保护,因此无论如何您都需要通过操作系统调用它们。
为了提供与其他答案略有不同的观点,我会这样回答。
(免责声明:我简化了一些事情,我给出的情况纯粹是假设性的,并且是作为展示概念的手段而不是100%真实的)。
从另一个angular度思考事情,想象一下,你刚刚写了一个简单的操作系统,具有基本的线程,窗口和内存pipe理function。 你想实现一个C ++库,让用户在C ++中编程,并做一些事情,如make窗口,绘制到窗口等。问题是,如何做到这一点。
首先,由于C ++编译为机器码,所以您需要定义一种使用机器码与C ++接口的方法。 这是函数进入的地方,函数接受参数并给出返回值,因此它们提供了在不同代码段之间传输数据的标准方式。 他们通过build立一个被称为调用约定的方法来做到这一点
调用约定规定了参数应该放在内存中的位置和方式,以便函数在执行时可以find它们。 当一个函数被调用时,调用函数将参数放在内存中,然后要求CPU跳转到另一个函数,在跳转到调用的位置之前,它会执行它的function。 这意味着被调用的代码可以是任何东西,它不会改变函数的调用方式。 但是,在这种情况下,函数背后的代码将与操作系统相关,并且将在操作系统的内部状态下运行。
所以,几个月后,你已经把所有的操作系统function整理出来了。 你的用户可以调用函数来创build窗口并绘制它们,它们可以创build线程和各种美妙的东西。 但是问题在于,你的操作系统的function和Linux的function或Windows的function是不一样的。 所以你决定你需要给用户一个标准的接口,以便他们可以编写可移植的代码。 这里是QT进来的地方。
正如你几乎肯定知道的那样,QT有很多有用的类和函数来完成操作系统所做的事情,但是以一种独立于底层操作系统的方式。 这样做的方式是QT提供的类和函数在用户看来是统一的,但是每个操作系统的函数背后的代码是不同的。 例如,QT的QApplication :: closeAllWindows()实际上将根据所使用的版本调用每个操作系统的专用窗口closures函数。 在Windows中,它很可能会调用CloseWindow(hwnd),而在使用X Window System的操作系统上,它可能会调用XDestroyWindow(display,window)。
很明显,一个操作系统有很多层次,所有这些层次都需要通过多种接口进行交互。 我甚至没有涉及到很多方面,但是要解释它们都需要很长时间。 如果您对操作系统的内部工作更感兴趣,我build议查看OS dev wiki 。
请记住,许多操作系统select将接口暴露给C / C ++的原因是它们编译为机器代码,它们允许将汇编指令与自己的代码混合在一起,并为程序员提供了很大的自由度。
再一次,这里有很多事情要做。 我想继续解释如何.so和.dll文件的库不必用C / C ++编写,并且可以用汇编或其他语言编写,但是我觉得如果我再添加一些,我也可以写一篇文章,尽可能多的我没有一个网站来承载它。
当你试图在屏幕上画一些东西的时候,你的代码会调用一些其他代码来调用一些其他的代码(等等),直到最终有一个“系统调用”,这是一个CPU可以执行的特殊指令。 如果编译器支持它们的“内部函数”(这是编译器通过将它们转换为CPU可以理解的特殊代码“专门”处理的函数),则这些指令可以用汇编语言编写,也可以用C ++编写。 他们的工作是告诉操作系统做些事情。
当一个系统调用发生时,一个被调用的函数调用另一个函数(等等),直到最终显示驱动程序被告知在屏幕上画一些东西。 此时,显示驱动程序查看物理内存中的一个特定区域,实际上它不是内存,而是一个可以像写入内存一样写入的地址范围。 但是,写入该地址范围会导致graphics硬件拦截内存写入,并在屏幕上绘制某些内容。
写入这个区域的内存是可以用C ++编写的,因为在软件方面它只是一个正常的内存访问。 只是硬件处理它不同。
所以这是如何工作的一个非常基本的解释。
您的C ++程序正在使用Qt库(也用C ++编码)。 Qt库将使用Windows CreateWindowEx函数(在kernel32.dll中用C语言编写)。 或者在Linux下,它可能使用Xlib (也用C语言编写),但是也可以发送X协议中的“ 请为我创build一个窗口 ”的原始字节。
与你的catch-22问题相关的是“第一个C ++编译器是用C ++编写的”的历史logging,尽pipe实际上它是一个C编译器,只有一些C ++概念,足以编译第一个版本,然后编译它自己。
同样,GCC编译器使用GCC扩展:首先编译为一个版本,然后用于重新编译自身。 (GCC构build说明)
我如何看到这个问题实际上是一个编译器问题。
以这种方式来看,你在Assembly中编写了一段代码(你可以用任何语言来完成),将你想要调用Z ++的新语言编译成汇编语言,为了简单起见,我们把它称为编译器(它是一个编译器) 。
现在你给这个编译器一些基本的function,这样你就可以编写int,string,数组等,实际上你给它足够的能力,以便你可以编写自己的Z + +编译器。 现在你有一个用Z ++编写的Z ++编译器,非常整洁。
更酷的是,现在你可以使用已有的function为该编译器添加function,从而通过使用先前的function扩展了Z ++语言的新function
举个例子,如果你编写足够的代码来绘制任何颜色的像素,那么你可以使用Z ++来展开它来绘制任何你想要的东西。
硬件是什么让这种情况发生。 您可以将graphics内存视为一个大数组(由屏幕上的每个像素组成)。 要绘制到屏幕上,您可以使用C ++或允许直接访问该内存的任何语言来写入此内存。 该内存恰好可以通过或位于显卡上。
在现代系统上直接访问graphics内存需要写一个驱动程序,因为各种限制,所以你使用间接的手段。 创build窗口的库(实际上就像任何其他图像一样),然后将该图像写入graphics存储器,然后GPU将其显示在屏幕上。 除了写入特定内存位置的能力之外,什么都不必添加到语言中,这是指针的用途。