语言如何扩展?

Med*_*ssi 207 c++ bootstrapping libraries

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

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

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

Som*_*ude 195

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

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

  • 一台电脑就像一个洋葱:穿过它会让你哭泣,但之后会有点好吃. (81认同)
  • 注意:`Qt`这里提供*它下面的层的抽象*,因为在Linux`Qt`上调用Linux API而不是WIN32 API. (56认同)
  • 我可能会更多地阐述Qt的例子,它只是出现,就像毫不费力地扩展c ++功能一样.当现实情况发生时,他们付出了很多努力来制作一个通用的API,以(解决)许多不同的"洋葱核心".它们是在非便携式非标准后端之上提供便携性的. (5认同)
  • @ChristopherPfohl是的,不得不使用它,因为我无法弄清楚计算机将如何像一盒巧克力.:) (3认同)

小智 59

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

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

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

  • 这个答案似乎表明"神奇"完全在于被称为其他语言,但实际上[大部分代码](https://www.openhub.net/p/linux/analyses/latest/languages_summary)构成最多现代操作系统是C(只有非常硬件连接或性能关键部分用汇编编写) - 并且[绝对有可能](https://www.haiku-os.org/)代替使用C++.关键是,没有"魔力",语言被创建来构建如此强大的抽象,一旦你可以与硬件交互,可能性几乎是无限的. (8认同)
  • @MedLarbiSentissi 1)不一定是其他编译器.一个单独的编译器能够编译多种语言(包括汇编),甚至能够编译C++并使用内联汇编,这是可能的(通常也是如此).2)根据特定的系统和编译器,使用C++调用这些函数可能确实可以使用某种接口文件来完成,但是那种接口文件可能已经是可以直接从C++使用的C(甚至是C++)头. (2认同)

dor*_*ron 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能够完成狮子会共享或计算机可以执行的操作.包装程序库所做的就是为您完成繁重的任务,以便您不必这样做.

  • 另外:内存映射I/O很常见,但不是全部.例如,PC通常使用x86的"I/O端口"来处理串行端口,磁盘驱动器等,这是一种完全不同的机制.(视频缓冲区通常是内存映射的,但视频模式等通常通过I/O端口控制.) (2认同)

Bas*_*tch 23

语言(如C++ 11)是纸上的规范,通常用英文书写.查看最新的C++ 11草案(或从ISO供应商处购买昂贵的最终规范).

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

您的C++实现通用工作在某些操作系统之上并与之通信(使用某些特定实现的代码,通常在某些系统库中).通常,通过系统调用完成通信.查找syscalls(2)中的实例,以获取Linux内核上可用的系统调用列表.

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

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

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

  • 作为参考,[ANSI出售C ++ 11规范](http://webstore.ansi.org/RecordDetail.aspx?sku=INCITS%2fISO%2fIEC+14882-2012)的价格略低一些,仅为60美元。(它曾经是通行证的一半,但实际上是通货膨胀。:P)它被标记为INCITS / ISO / IEC 14882,但至少与ISO提供的基本规格相同。不确定勘误/ TR。 (2认同)

Dav*_*eau 19

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

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

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

  • 缺少的重要一点是那些系统调用绝不是神奇的.它们由内核提供服务,内核通常用C(++)编写.而且,系统调用甚至不是必需的.在没有内存保护的基本操作系统中,可以通过将像素直接放入硬件帧缓冲区来绘制窗口. (3认同)

Kri*_*osh 15

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

int main(int argc, char **argv)
{
   return 0
}
Run Code Online (Sandbox Code Playgroud)

现在编译此文件(使用开发人员命令提示符)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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

extern char getch();

void main(int, char **)
{
  getch();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


Phi*_*lip 10

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

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

考虑自己编写这样的函数

void addExclamation(std::string &str)
{
    str.push_back('!');
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


ths*_*hst 8

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

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

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


Joe*_*Joe 7

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

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

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


Dan*_*lor 7

首先,我认为有一点误解

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

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

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

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

这是一个很好的教程:http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
它适用于Linux,但原理是相同的.

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


Pha*_*rap 7

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

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

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

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

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

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

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

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

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

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


Meh*_*dad 6

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

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

  • Afaik系统调用不是真正的cpu指令,与内在函数无关.它更像是操作系统内核的一个功能,它与设备通信. (4认同)
  • @MatthiasB:嗯,你错了,因为`syscall`(和它的堂兄`sysenter`)确实是一个CPU指令. (3认同)
  • 系统调用使用软件中断完成.像`sysenter`这样的指令是优化的调用路径,因为中断处理程序使用的上下文切换并不像所有人想要的那么快,但从根本上说,它仍然是软件生成的中断,同时通过向OS内核安装的处理程序进行处理.由`sysenter`执行的上下文切换过程的一部分是改变处理器中的模式位以设置响铃0 - 对所有特权指令,寄存器以及存储器和I/O区域的完全访问. (3认同)
  • 这仅仅是提高答案的暗示,因为我自己并不清楚.不要将其视为个人攻击或其他任何东西. (2认同)