微控制器有 C 标准吗?
微控制器有特殊的 C 标准吗?
我问这个问题是因为到目前为止,当我在 Windows 操作系统下编程时,使用哪个编译器并不重要。如果我有一个 C99 的编译器,我就知道我可以用它做什么。
但最近我开始用 C 语言为微控制器编程,我很震惊,即使它的基础知识仍然是 C 语言,比如循环、变量创建等,但有一些语法类型是我在台式计算机的 C 语言中从未见过的。此外,语法随着版本的不同而变化。我使用AVR-GCC编译器,在以前的版本中,您使用端口I/O的函数,现在您可以在新版本中像变量一样处理端口。
什么定义了哪些函数以及如何将它们实现到编译器中并且仍然被称为 C?
Is there any special C standard for microcontrollers?
I ask because so far when I programmed something under Windows OS, it doesn't matter which compiler I used. If I had a compiler for C99, I knew what I could do with it.
But recently I started to program in C for microcontrollers, and I was shocked, that even it's still C in its basics, like loops, variables creation and so, there is some syntax type I have never seen in C for desktop computers. And furthermore, the syntax is changing from version to version. I use AVR-GCC compiler, and in previous versions, you used a function for port I/O, now you can handle a port like a variable in the new version.
What defines what functions and how to have them to be implemented into the compiler and still have it be called C?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
不,有 ISO C 标准。由于许多小型设备具有需要支持的特殊体系结构功能,因此许多编译器都支持语言扩展。例如,因为 8051 具有位可寻址 RAM,所以可以提供
_bit
数据类型。它还有一个 哈佛架构,因此提供了关键字来指定不同的内存地址空间,其中一个地址单独无法解决,因为需要不同的指令来解决这些空间。此类扩展将在编译器文档中明确指出。此外,符合标准的编译器中的扩展应该以下划线为前缀。然而,许多提供了朴素的别名以实现向后兼容性,因此应弃用它们的使用。由于 Windows API 是标准化的(由 Microsoft 制定),并且它仅在 x86 上运行,因此无需考虑架构变化。也就是说,您可能仍然会看到 FAR 和 NEAR API 中的宏,这是对 16 位 x86 分段寻址的回归,这也需要编译器扩展处理。
我不确定这意味着什么。典型的微控制器应用程序没有操作系统或简单的内核,您应该会看到更多的“裸机”或“系统级”代码,因为没有广泛的操作系统 API 和设备驱动程序接口来完成大量工作给你的引擎盖。所有这些库调用都只是这样;它们不是语言的一部分;都是C语言;只是投入不同的工作。
例如...?
我对此表示怀疑。再次;例如...?
这并不是由于语言或编译器的变化,而更可能是简单的“预处理器魔法”。在 AVR 上,所有 I/O 都是内存映射的,因此,如果您包含设备支持标头,它可能具有如下声明:
然后您可以写入:
将 0xFF 写入地址 0x100 处的内存映射寄存器。您只需查看头文件即可确切了解它是如何工作的。
GCC 文档描述了目标特定的变化; 此处专门处理 AVR 第 6.36.8 节和 3.17.3。如果将其与 GCC 支持的其他目标进行比较,您会发现它的扩展非常少,这可能是因为 AVR 架构和指令集是专门为干净、高效地实现没有扩展的 C 编译器而设计的。
重要的是要认识到 C 编程语言是与其库不同的实体,并且库提供的函数与您自己编写的函数没有什么不同 - 它们不是语言的一部分 - 因此它可以是 C,而不需要任何图书馆。最终,库函数是使用相同的基本语言元素编写的。您不能期望 Win32 API 中存在的抽象级别存在于用于微控制器的库中。在大多数情况下,您可以期望至少实现 C 标准库 的子集因为它被设计为一个系统级库,几乎没有目标硬件依赖性。
多年来,我一直在为嵌入式和桌面系统编写 C 和 C++,并且没有意识到您似乎感知到的巨大差异,因此只能假设它们是对 C 语言构成的误解的结果。以下书籍可能会有所帮助。
No, there is the ISO C standard. Because many small devices have special architecture features that need to be supported, many compilers support language extensions. For example because an 8051 has bit addressable RAM, a
_bit
data type may be provided. It also has a Harvard architecture, so keywords are provided for specifying different memory address spaces which an address alone does not resolve since different instructions are required to address these spaces. Such extensions will be clearly indicated in the compiler documentation. Moreover, extensions in a conforming compiler should be prefixed with an underscore. However, many provide unadorned aliases for backward compatibility, and their use should be deprecated.Because the Windows API is standardized (by Microsoft), and it only runs on x86, so there is no architectural variation to consider. That said, you may still see FAR, and NEAR macros in APIs, and that is a throwback to 16-bit x86 with its segmented addressing, which also required compiler extensions to handle.
I am not sure what that means. A typical microcontroller application has no OS or a simple kernel, you should expect to see a lot more 'bare metal' or 'system-level' code, because there are no extensive OS APIs and device driver interfaces to do lots of work under the hood for you. All those library calls are just that; they are not part of the language; it is the same C language; just put to different work.
For example...?
I doubt it. Again; for example...?
That is not down to changes in the language or compiler, but more likely simple 'preprocessor magic'. On AVR, all I/O is memory mapped, so if for example you include the device support header, it may have a declaration such as:
You can then write:
to write 0xFF to the memory mapped register at address 0x100. You could just take a look at the header file and see exactly how it does it.
The GCC documentation describes target specific variations; AVR is specifically dealt with here in section 6.36.8, and in 3.17.3. If you compare that with other targets supported by GCC, it has very few extensions, perhaps because the AVR architecture and instruction set were specifically designed for clean and efficient implementation of a C compiler without extensions.
It is important to realise that the C programming language is a distinct entity from its libraries, and that functions provided by libraries are no different from the ones you might write yourself - they are not part of the language - so it can be C with no library whatsoever. Ultimately, library functions are written using the same basic language elements. You cannot expect the level of abstraction present in, say, the Win32 API to exist in a library intended for a microcontroller. You can in most cases expect at least a subset of the C Standard Library to be implemented since it was designed as a systems level library with few target hardware dependencies.
I have been writing C and C++ for embedded and desktop systems for years and do not recognise the huge differences you seem to perceive, so can only assume that they are the result of a misunderstanding of what constitutes the C language. The following books may help.
嵌入式系统很奇怪,有时对“标准”C 有例外。
从一个系统到另一个系统,您将有不同的方法来执行某些操作,例如声明中断,或定义哪些变量位于不同的内存段中,或运行“内在函数”(伪函数)直接映射到汇编代码),或执行内联汇编代码。
但控制流(for/if/while/switch/case)以及变量和函数声明的基础知识应该是全面相同的。
这不是 C 语言的一部分;而是 C 语言的一部分。这是设备支持库的一部分。这是每个制造商都必须记录的内容。
Embedded systems are weird and sometimes have exceptions to "standard" C.
From system to system you will have different ways to do things like declare interrupts, or define what variables live in different segments of memory, or run "intrinsics" (pseudo-functions that map directly to assembly code), or execute inline assembly code.
But the basics of control flow (for/if/while/switch/case) and variable and function declarations should be the same across the board.
That's not part of the C language; that's part of a device support library. That's something each manufacturer will have to document.
C 语言假定 von Neumann 架构(所有代码和数据的一个地址空间),并非所有架构实际上都具有,但大多数桌面/服务器类机器确实具有(或至少在操作系统的帮助下存在)。为了解决这个问题而不编写可怕的程序,C 编译器(在链接器的帮助下)通常支持一些扩展,以帮助有效地利用多个地址空间。所有这些都可以对程序员隐藏,但它通常会减慢程序和数据并使程序和数据膨胀。
至于如何访问设备寄存器——在不同的桌面/服务器类机器上,这也有很大不同,但由于为在这些机器的常见现代操作系统(Mac OS X、Windows、BSD 或 Linux)下运行而编写的程序不适用通常不直接访问硬件,这不是问题。不过,操作系统代码必须处理这些问题。这通常是通过定义在不同体系结构上以不同方式实现的宏和/或函数来完成的,甚至在单个系统上具有多个版本,以便驱动程序可以为特定设备(例如以太网芯片)工作,无论它是在PCI 卡或 USB 适配器(可能插入插入 PCI 插槽的 USB 卡),或直接映射到处理器的地址空间。
此外,C 标准库对托管使用它的程序的系统做出了比编译器(和语言本身)更多的假设 (C 标准库)。当没有通用操作系统或文件系统时,这些事情就没有意义。
fopen
在没有文件系统的系统上没有任何意义,甚至printf
也可能不容易定义。至于 AVR-GCC 及其库的作用 - 有有很多内容涉及如何完成此操作。 AVR 是一个带有内存映射设备控制寄存器的 Harvard 架构,特殊功能寄存器和通用寄存器(存储器地址0-31),以及用于代码和常量数据的不同地址空间。这已经超出了标准 C 的假设范围。一些寄存器(通用、特殊和设备控制)可通过特殊指令访问,例如翻转单个位和读/写某些多字节寄存器(多指令操作)隐式阻止下一条指令的中断(因此操作的后半部分可能会发生)。这些是桌面 C 程序不需要了解的东西,因为 AVR-GCC 来自常规 GCC,它最初也不理解所有这些事情。这意味着编译器并不总是使用最好的指令来访问控制寄存器,因此:
会变成:
因为 AVR 通常必须在其通用寄存器中包含一些内容才能对它们进行位操作,尽管对于某些内存位置,这不是真的。 AVR-GCC 必须进行修改,以认识到当某些操作中使用的变量的地址在编译时已知并且位于某个范围内时,它可以使用不同的指令来执行这些操作。在此之前,AVR-GCC 只是为您提供了一些宏(看起来像函数),它们具有内联汇编来执行此操作(并使用 GCC 现在使用的单指令实现)。如果它们不再提供这些操作的宏版本,那么这可能是一个糟糕的选择,因为它破坏了旧代码,但是一旦实现有效且原子地执行此操作的能力良好,就允许您像普通变量一样访问这些寄存器。
The C language assumes a von Neumann architecture (one address space for all code and data) which not all architectures actually have, but most desktop/server class machines do have (or at least present with the aid of the OS). To get around this without making horrible programs, the C compiler (with help from the linker) often support some extensions that aid in making use of multiple address spaces efficiently. All of this could be hidden from the programmer, but it would often slow down and inflate programs and data.
As far as how you access device registers -- on different desktop/server class machines this is very different as well, but since programs written to run under common modern OSes for these machines (Mac OS X, Windows, BSDs, or Linux) don't normally access hardware directly, this isn't an issue. There is OS code that has to deal with these issues, though. This is usually done through defining macros and/or functions that are implemented differently on different architectures or even have multiple versions on a single system so that a driver could work for a particular device (such an Ethernet chip) whether it were on a PCI card or a USB dongle (possibly plugged into a USB card plugged into a PCI slot), or directly mapped into the processor's address space.
Additionally, the C standard library makes more assumptions than the compiler (and language proper) about the system that hosts the programs that use it (the C standard library). These things just don't make sense when there isn't a general purpose OS or filesystem.
fopen
makes no sense on a system without a filesystem, and evenprintf
might not be easily definable.As far as what AVR-GCC and its libraries do -- there are lots of stuff that goes into how this is done. The AVR is a Harvard architecture with memory mapped device control registers, special function registers, and general purpose registers (memory addresses 0-31), and a different address space for code and constant data. This already falls outside of what standard C assumes. Some of the registers (general, special, and device control) are accessible via special instructions for things like flipping single bits and read/writing to some multi-byte registers (a multi-instruction operation) implicitly blocks interrupts for the next instruction (so that the second half of the operation can happen). These are things that desktop C programs don't have to know anything about, and since AVR-GCC comes from regular GCC, it didn't initially understand all of these things either. That meant that the compiler wouldn't always use the best instructions to access control registers, so:
would have turned into:
because AVR generally has to have things in its general purpose registers to do bit operations on them, though for some memory locations this isn't true. AVR-GCC had to be altered to recognize that when the address of a variable used in certain operations is known at compile time and lies within a certain range, it can use different instructions to preform these operations. Prior to this, AVR-GCC just provided you with some macros (that looked like functions) that had inline assembly to do this (and use the single instruction inplemenations that GCC now uses). If they no longer provide the macro versions of these operations then that's probably a bad choice since it breaks old code, but allowing you to access these registers as though they were normal variables once the ability to do so efficiently and atomically was implemented is good.
我从未见过没有一些特定于控制器的扩展的微控制器 C 编译器。有些编译器比其他编译器更接近 ANSI 标准,但对于许多微控制器来说,在性能和 ANSI 合规性之间需要权衡。
在许多 8 位微控制器上,甚至在一些 16 位微控制器上,访问堆栈帧上的变量速度很慢。有些编译器将始终在运行时堆栈上分配自动变量,尽管需要额外的代码,有些编译器会在编译时分配自动变量(允许不同时存在的变量重叠),有些则允许控制行为使用命令行选项或#pragma 指令。在为此类机器编码时,我有时喜欢
#define
一个名为“auto”的宏,如果它可以帮助工作更快,它会被重新定义为“static”。一些编译器有多种内存存储类别。通过将事物声明为合适的存储类别,您可以极大地提高性能。例如,基于 8051 的系统可能有 96 字节的“数据”内存, 224 字节的“idata”内存与前 96 字节重叠,以及 4K 的“xdata”内存。
可以直接访问“数据”内存中的变量。
“idata”内存中的变量只能通过将其地址加载到一字节指针寄存器中来访问。在有必要的情况下访问它们不会产生额外的开销,因此 idata 内存非常适合数组。如果数组
q
存储在 idata 内存中,则对q[i]
的引用将与在数据内存中一样快,尽管对的引用q[0]
会更慢(在数据内存中,编译器可以预先计算地址并在没有指针寄存器的情况下访问它;在 idata 内存中这是不可能的)。xdata 内存中的变量的访问速度比其他类型中的变量慢得多,但可用的 xdata 内存要多得多。
如果告诉 8051 编译器默认将所有内容放入“data”中,那么如果变量总数超过 96 个字节并且没有指示编译器将任何内容放入其他位置,就会“耗尽内存”。如果默认情况下将所有内容放入“xdata”中,则可以使用更多内存而不会达到限制,但所有内容都会运行得更慢。最好的办法是将直接访问的常用变量放在“data”中,将间接访问的常用变量和数组放在“idata”中,将不常用的变量和数组放在“xdata”中。
I have never seen a C compiler for a microcontroller which did not have some controller-specific extensions. Some compilers are much closer to meeting ANSI standards than others, but for many microcontrollers there are tradeoffs between performance and ANSI compliance.
On many 8-bit microcontrollers, and even some 16-bit ones, accessing variables on a stack frame is slow. Some compilers will always allocate automatic variables on a run-time stack despite the extra code required to do so, some will allocate automatic variables at compile time (allowing variables that are never live simultaneously to overlap), and some allow the behavior to be controlled with a command-line options or
#pragma
directives. When coding for such machines, I sometimes like to#define
a macro called "auto" which gets redefined to "static" if it will help things work faster.Some compilers have a variety of storage classes for memory. You may be able to improve performance greatly by declaring things to be of suitable storage classes. For example, an 8051-based system might have 96 bytes of "data" memory, 224 bytes of "idata" memory which overlaps the first 96 bytes, and 4K of "xdata" memory.
Variables in "data" memory may be accessed directly.
Variables in "idata" memory may only be accessed by loading their address into a one-byte pointer register. There is no extra overhead accessing them in cases where that would be necessary anyway, so idata memory is great for arrays. If array
q
is stored in idata memory, a reference toq[i]
will be just as fast as if it were in data memory, though a reference toq[0]
will be slower (in data memory, the compiler could pre-compute the address and access it without a pointer register; in idata memory that is not possible).Variables in xdata memory are far slower to access than those in other types, but there's a lot more xdata memory available.
If one tells an 8051 compiler to put everything in "data" by default, one will "run out of memory" if one's variables total more than 96 bytes and one hasn't instructed the compiler to put anything elsewhere. If one puts everything in "xdata" by default, one can use a lot more memory without hitting a limit, but everything will run slower. The best is to place frequently-used variables that will be directly accessed in "data", frequently-used variables and arrays that are indirectly accessed in "idata", and infrequently-used variables and arrays in "xdata".
绝大多数标准 C 语言是微控制器通用的。中断的约定往往略有不同,但并非总是如此。
将端口视为变量是因为大多数微控制器上的寄存器都映射到内存中的位置,因此通过写入适当的内存位置(定义为内存中具有预设位置的变量),您可以设置该值港口。
The vast majority of the standard C language is common with microcontrollers. Interrupts do tend to have slightly different conventions, although not always.
Treating ports like variables is a result of the fact that the registers are mapped to locations in memory on most microcontrollers, so by writing to the appropriate memory location (defined as a variable with a preset location in memory), you set the value on that port.
正如之前的贡献者所说,没有这样的标准,主要是由于架构不同。
话虽如此,Dynamic C(由Rabbit Semiconductor) 被描述为“具有实时扩展的 C”。据我所知,编译器仅针对 Rabbit 处理器,但还有一些有用的附加关键字(例如,costate、cofunc 和 waitfor),以及一些真正的特性(例如,
#use mylib.lib
而不是#include mylib.h
- 并且没有链接器),以及 ANSI C 的一些遗漏(例如,没有文件范围的静态变量)。但它仍然被描述为“C”。
As previous contributors have said, there is no standard as such, mainly due to different architectures.
Having said that, Dynamic C (sold by Rabbit Semiconductor) is described as "C with real-time extensions". As far as I know, the compiler only targets Rabbit processors, but there are useful additional keywords (for example, costate, cofunc, and waitfor), some real peculiarities (for example,
#use mylib.lib
instead of#include mylib.h
- and no linker), and several omissions from ANSI C (for example, no file-scope static variables).It's still described as 'C' though.
Wiring 具有基于 C 的语言语法。也许您可能想看看是什么造就了它。
Wiring has a C-based language syntax. Perhaps you might want to see what makes it as such.