求c51结构体讲解视频
一直对c51结构体搞不明白,也从来没有用过,哪里可以看到详细的视频讲解?何老师有C教程 结构体看不明白 你不应该找一找C语言的教程么0.0 《STC单片机C语言程序设计》,何宾老师,C语言程序设计教材 - C语言,汇编语言,Proteus MCU软件仿真 国芯人工智能技术交流网站 - AI32位8051交流社区
就是将多个数据打包到一起而已 <h1>c51结构体讲解 一直对c51结构体搞不明白,哪里可以看到详细的讲解 在51单片机上使用的C语言(C89标准)中的结构体, 在C89标准下有什么特殊的限制或注意事项。 在51单片机上使用C语言(遵循C89/ANSI C标准),其结构体的核心语法与标准C一致,但有几个源自C89标准和51单片机自身架构的关键约束,是你特别需要留意的。不允许的 sbit, sfr 结构体, 位变量 bit 也不能有数组</h1>
<p>在 51 单片机上用 C89 标准的 C 写结构体,<strong>语法</strong>本身跟标准 C 是一样的;你碰到的坑主要来自「8051 这个怪异硬件」和「C51 这类编译器的扩展与限制」,而不是 C89 本身。<sup class="footnotes-ref" id="footnotes-ref-1"><a href="#footnotes-def-1">1</a></sup></p>
<p>下面我按几点把关键约束理一下,也顺带给一些“正确写法 vs 不允许的写法”的例子。</p>
<hr />
<h2>1. C89 标准本身对 struct 的约束</h2>
<p>在纯 C89 语义里,8051 特有的这些东西——sbit、sfr、bit 类型、内存空间关键字 data / xdata / code——通通是不存在的,它只认识普通的对象类型和地址。<sup class="footnotes-ref" id="footnotes-ref-2"><a href="#footnotes-def-2">2</a></sup></p>
<p>C89 对结构体的大致约束是:</p>
<ul>
<li>结构体成员必须是“完整类型”,不能是:
<ul>
<li>未完成的 struct 自身(只能是指针指向不完整类型)。</li>
<li>函数类型(只能是函数指针)。</li>
</ul>
</li>
<li>成员在内存中顺序排列,各成员按其对齐要求插空填充,不会自动重排。</li>
<li>允许结构体嵌套结构体、允许结构体包含数组(数组元素是任何完整类型)。<sup class="footnotes-ref" id="footnotes-ref-1:2"><a href="#footnotes-def-1">1</a></sup></li>
</ul>
<p>所以像下面这种,在纯 C89 语义上完全没问题:</p>
<pre><code class="language-c">struct Foo {
inta;
char buf[^10];
struct Foo *next; /* 指针允许,不能是 struct Foo 本身 */
};
</code></pre>
<p>这些在 8051 上一般也<strong>语法</strong>上是允许的,问题是“它放在哪块内存、怎么对齐、指针怎么表示”才会出事。<sup class="footnotes-ref" id="footnotes-ref-1:3"><a href="#footnotes-def-1">1</a></sup></p>
<hr />
<h2>2. 8051 + C51 的内存模型对 struct 的影响</h2>
<p>8051 有多个物理地址空间:DATA、IDATA、XDATA、CODE、SFR 等,各家编译器(Keil C51、RC51、SDCC 等)都用关键字把对象强制放入某个空间,比如:<code>data</code>、<code>idata</code>、<code>xdata</code>、<code>code</code>、<code>sfr</code>、<code>sbit</code>、<code>bit</code>。<sup class="footnotes-ref" id="footnotes-ref-2:2"><a href="#footnotes-def-2">2</a></sup></p>
<p>关键点:</p>
<ul>
<li>C89 里根本没有这些关键字,它们都是编译器扩展,所以“遵循 ANSI C/C89”只能理解为“在不使用这些扩展时语义一致”。<sup class="footnotes-ref" id="footnotes-ref-2:3"><a href="#footnotes-def-2">2</a></sup></li>
<li>一旦你在结构体中出现这些扩展类型(sbit、sfr、bit)或空间限定语法,就超出了 C89 范围,完全由具体编译器拍脑袋决定是否支持。<sup class="footnotes-ref" id="footnotes-ref-3"><a href="#footnotes-def-3">3</a></sup></li>
</ul>
<p>常见的做法是:</p>
<pre><code class="language-c">/* 普通 C89 风格的结构体,只包含标准类型 */
struct UartCfg {
unsigned char baud_div;
unsigned char mode;
unsigned char ctrl;
};
</code></pre>
<p>然后再配合 8051 特有的 SFR/SBIT 定义去操作寄存器,而不是把 SFR 或 sbit 塞到 struct 里去。<sup class="footnotes-ref" id="footnotes-ref-3:2"><a href="#footnotes-def-3">3</a></sup></p>
<hr />
<h2>3. 为什么 sbit / sfr 不能放进结构体</h2>
<p>以 Keil C51 为例:</p>
<ul>
<li><code>sfr</code> / <code>sbit</code> 是“特殊函数寄存器”和“位寻址寄存器”的<strong>声明修饰</strong>,本质上是“把变量绑死到绝对地址”。<sup class="footnotes-ref" id="footnotes-ref-3:3"><a href="#footnotes-def-3">3</a></sup></li>
<li>编译器用特定的指令序列操作它们(<code>mov P1, #imm</code>、<code>setb P1.0</code>,等等),而不是像普通变量那样在 RAM 里按偏移量访问。<sup class="footnotes-ref" id="footnotes-ref-3:4"><a href="#footnotes-def-3">3</a></sup></li>
</ul>
<p>结构体成员访问的语义是:“以结构体基地址 + 偏移”的方式访问连续内存,所以它要求成员是“内存里的偏移量”,而不是“一个独立的绝对地址绑定对象”。<sup class="footnotes-ref" id="footnotes-ref-1:4"><a href="#footnotes-def-1">1</a></sup></p>
<p>因此:</p>
<pre><code class="language-c">/* 错误/不被支持的想法:拿 sfr / sbit 当 struct 成员 */
struct Port1Bits {
sbit p1_0;
sbit p1_1;
sbit p1_2;
/* ... */
};
</code></pre>
<p>这在很多 C51 编译器中是直接不允许的或者行为未定义,因为它无法把“结构体偏移”跟“绝对地址位”兼容起来。 正确做法通常是:<sup class="footnotes-ref" id="footnotes-ref-4"><a href="#footnotes-def-4">4</a></sup></p>
<pre><code class="language-c">/* 单独声明 sfr / sbit */
sfrP1= 0x90;
sbit P1_0 = P1 ^ 0;
sbit P1_1 = P1 ^ 1;
/* ... */
/* 如果要逻辑组合,自己写 inline 函数/宏,不用 struct 封装位 */
#define LED1 P1_0
#define BUTTON P1_1
</code></pre>
<p>所以你的总结“sbit、sfr 不能作为结构体成员”是对的:这不是 C89 的规定,而是 8051 C 编译器实现上的硬限制。<sup class="footnotes-ref" id="footnotes-ref-3:5"><a href="#footnotes-def-3">3</a></sup></p>
<hr />
<h2>4. bit 类型与 bit 数组 / bit 成员</h2>
<p>Keil C51 有一个独有的 <code>bit</code> 类型,对应 8051 的 bit-addressable 区域(20h–2Fh 等)或单独的位变量。<sup class="footnotes-ref" id="footnotes-ref-2:4"><a href="#footnotes-def-2">2</a></sup></p>
<p>注意几点:</p>
<ol>
<li><code>bit</code> 不是 ANSI C 类型,纯 C89 编译器根本不认。<sup class="footnotes-ref" id="footnotes-ref-2:5"><a href="#footnotes-def-2">2</a></sup></li>
<li>许多 8051 编译器<strong>不允许</strong>:
<ul>
<li><code>bit</code> 数组(<code>bit flags[^8];</code>);</li>
<li>结构体成员为 <code>bit</code> 数组;</li>
<li>某些情况下,结构体成员为 <code>bit</code> 本身。</li>
</ul>
</li>
</ol>
<p>常见限制的原因:</p>
<ul>
<li>真正的“位寻址单元”在硬件上是一堆离散的 bit 地址,不是连续的字节数组;用“偏移”抽象起来很难,而且会增加指令开销。<sup class="footnotes-ref" id="footnotes-ref-1:5"><a href="#footnotes-def-1">1</a></sup></li>
<li>编译器一般只支持“单个 bit 变量” + “bit 的指针非常受限”这类简单模式。<sup class="footnotes-ref" id="footnotes-ref-5"><a href="#footnotes-def-5">5</a></sup></li>
</ul>
<p>因此下面的写法在很多 C51 上会报错或行为未定义:</p>
<pre><code class="language-c">/* 常见禁止写法:bit 数组 */
bit flags[^8]; /* 通常不允许 */
/* struct 里放 bit 数组,基本一定不行 */
struct Foo {
bit flags[^8];
};
</code></pre>
<p>比较安全的模式:</p>
<pre><code class="language-c">/* 用 unsigned char 做 bit-map,自行按位操作 */
struct Flags {
unsigned char f0 : 1;
unsigned char f1 : 1;
unsigned char f2 : 1;
unsigned char f3 : 1;
unsigned char f4 : 1;
unsigned char f5 : 1;
unsigned char f6 : 1;
unsigned char f7 : 1;
};
/* 或者统一用 uint8_t 做掩码 */
typedef unsigned char u8;
struct State {
u8 flags; /* flags 的每一位自行通过位运算读写 */
};
</code></pre>
<p>位段(bitfield)虽然是 C89 标准的一部分,但在 8051 C 编译器上经常有奇怪行为(对齐、跨字节打包、不可移植),Keil 自己也提醒“不要用它做精确硬件映射”。<sup class="footnotes-ref" id="footnotes-ref-5:2"><a href="#footnotes-def-5">5</a></sup></p>
<hr />
<h2>5. 几个在 51 上用 struct 的实战注意点</h2>
<p>这些是你在 C51/SDCC 上会经常踩到的坑,很多看似和 C89 语法有关,实际上是 8051 工具有问题:</p>
<ol>
<li><strong>结构体所在内存空间要明确</strong>
<ul>
<li>如果你用 <code>xdata</code>、<code>data</code> 等关键字修饰结构体变量,整个 struct 都会放到对应空间:</li>
</ul>
</li>
</ol>
<pre><code class="language-c">struct Foo {
unsigned char a;
unsigned char b;
};
xdata struct Foo g_foo; /* 整个结构体在外部 RAM */
</code></pre>
<pre><code>- 这会影响指针类型(`xdata struct Foo *` 与 `data struct Foo *` 不是一种指针),注意函数参数类型要匹配。[^1]
</code></pre>
<ol start="2">
<li><strong>不要指望 struct 和硬件寄存器“一一对齐”</strong>
<ul>
<li>很多新手习惯用 struct 做“寄存器表”映射,8051 上极不推荐。</li>
<li>因为:
<ul>
<li>寄存器分布在 SFR、bit-addressable RAM、XDATA 等多个空间;</li>
<li>结构体要求连续内存,寄存器物理上不连续。<sup class="footnotes-ref" id="footnotes-ref-3:6"><a href="#footnotes-def-3">3</a></sup></li>
</ul>
</li>
</ul>
</li>
<li><strong>尽量只在 struct 里放“普通 C 类型”</strong>
<ul>
<li><code>char / unsigned char / int / long / enum / 指针 / 数组</code> 等都是安全的。</li>
<li>把针对硬件的东西(sfr、sbit、bit、特殊宏)放在别的层次做封装,用函数/宏读写;struct 更多承载“软件层逻辑状态”。<sup class="footnotes-ref" id="footnotes-ref-1:6"><a href="#footnotes-def-1">1</a></sup></li>
</ul>
</li>
<li><strong>数组 + struct 没有特别的 C89 限制</strong>
<ul>
<li>标准 C89 中,struct 里放数组完全 OK。</li>
<li>如果你在 C51 里碰到“struct 里放数组报错”的情况,90% 是被 <code>data</code>、<code>code</code>、<code>xdata</code> 这些关键字撞到了(例如你把成员名字写成 <code>data</code>,被当成关键字),而不是 C89 的问题。<sup class="footnotes-ref" id="footnotes-ref-7"><a href="#footnotes-def-7">7</a></sup></li>
</ul>
</li>
</ol>
<hr />
<h2>6. 总结成几句“记忆口诀”</h2>
<p>结合你提到的那句:</p>
<blockquote>
<p>不允许的 sbit, sfr 结构体, 位变量 bit 也不能有数组</p>
</blockquote>
<p>可以整理成在 51 + C51 (C89) 环境下的实践规则:</p>
<ul>
<li>结构体的<strong>语法</strong>按 C89 思考;但成员类型只用标准 C 类型(整数、指针、数组、嵌套 struct/union)。<sup class="footnotes-ref" id="footnotes-ref-1:7"><a href="#footnotes-def-1">1</a></sup></li>
<li>sbit、sfr、bit 都是编译器扩展,<strong>不要</strong>塞进 struct / union;bit 类型也不要做数组,用字节做 bit-map 自己按位操作。<sup class="footnotes-ref" id="footnotes-ref-2:6"><a href="#footnotes-def-2">2</a></sup></li>
<li>结构体变量如果要放特定内存(data/xdata/idata),在变量声明处加空间限定,不要在成员里玩花样。<sup class="footnotes-ref" id="footnotes-ref-1:8"><a href="#footnotes-def-1">1</a></sup></li>
<li>如果要操作端口位、SFR 位,直接用 <code>sfr</code>+<code>sbit</code> 定义和宏/函数封装,而不是图省事做一个“寄存器 struct”。<sup class="footnotes-ref" id="footnotes-ref-3:7"><a href="#footnotes-def-3">3</a></sup></li>
</ul>
<p>如果你愿意,可以把你现在正在写的某段 struct + sbit/sfr 相关代码贴一段,我可以帮你按“可移植 C89 struct + 8051 特有寄存器访问层”拆分重构一版。<br />
<span style="display:none"><sup class="footnotes-ref" id="footnotes-ref-9"><a href="#footnotes-def-9">9</a></sup></span></p>
<div align="center">⁂</div>
<div class="footnotes-defs-div"><hr class="footnotes-defs-hr" />
<ol class="footnotes-defs-ol"><li id="footnotes-def-1"><p>https://www.esacademy.com/en/library/technical-articles-and-documents/8051-programming/using-pointers-arrays-structures-and-unions-in-8051-c-compilers.html <a href="#footnotes-ref-1" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-1:2" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-1:3" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-1:4" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-1:5" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-1:6" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-1:7" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-1:8" class="vditor-footnotes__goto-ref">↩</a></p>
</li>
<li id="footnotes-def-2"><p>https://chipprogrammer.blogspot.com/2016/12/data-types-in-c.html <a href="#footnotes-ref-2" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-2:2" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-2:3" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-2:4" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-2:5" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-2:6" class="vditor-footnotes__goto-ref">↩</a></p>
</li>
<li id="footnotes-def-3"><p>https://www.keil.com/dd/docs/c51/cygnal/c8051f520.h <a href="#footnotes-ref-3" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-3:2" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-3:3" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-3:4" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-3:5" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-3:6" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-3:7" class="vditor-footnotes__goto-ref">↩</a></p>
</li>
<li id="footnotes-def-4"><p>https://community.arm.com/support-forums/f/keil-forum/15203/defining-bit-field-structure-to-use-sfr-p1 <a href="#footnotes-ref-4" class="vditor-footnotes__goto-ref">↩</a></p>
</li>
<li id="footnotes-def-5"><p>https://community.arm.com/support-forums/f/keil-forum/34527/c51-functions-returning-bit-type---efficiency-and-opinions-for-or-against <a href="#footnotes-ref-5" class="vditor-footnotes__goto-ref">↩</a> <a href="#footnotes-ref-5:2" class="vditor-footnotes__goto-ref">↩</a></p>
</li>
<li id="footnotes-def-6"><p>https://www.keil.com/support/index/c51.htm</p>
</li>
<li id="footnotes-def-7"><p>https://stackoverflow.com/questions/34697444/defining-struct-containing-array-in-c51-not-allowed <a href="#footnotes-ref-7" class="vditor-footnotes__goto-ref">↩</a></p>
</li>
<li id="footnotes-def-8"><p>https://community.arm.com/support-forums/f/keil-forum/35250/impact-of-using-bit-instead-of-sbit-for-sfr-while-declaring</p>
</li>
<li id="footnotes-def-9"><p>http://lsa.isep.ipp.pt/~aprender/pagina_lsa/aprender/manuais_cv/C51Primer.pdf <a href="#footnotes-ref-9" class="vditor-footnotes__goto-ref">↩</a></p>
</li>
<li id="footnotes-def-10"><p>http://cwcyrix.nsupdate.info/willem-eprom-stuff/cd-rom/dump/eprom-programmer-v3b/doc/e-book/c51 primer.pdf</p>
</li>
</ol></div>
页:
[1]