AI摘要 Fibonacci序列在UI/UX设计中扮演着重要角色，其相邻项比值趋近于黄金比例，带来舒适自然的视觉体验。在UI设计中，Fibonacci可用于布局、网格系统、字体层级、容器与卡片大小、间距和配色等方面，有助于形成自然层次、清晰的重点和舒适的视觉流，提高信息的易吸收性，并在复杂界面中形成和谐。Fibonacci的应用不仅提升了视觉节奏、信息层级、可读性、界面统一性，还降低了设计复杂度，是构建设计系统的有效方式。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 📘 《Fibonacci 在 UI 设计中的应用》Fibonacci Sequence in UI / UX Design一、什么是 Fibonacci？Fibonacci（斐波那契） 是一组遵循“前两项之和等于后一项”的数学序列：[1, 1, 2, 3, 5, 8, 13, 21, …]它的相邻项比值趋近于 黄金比例（0.618 / 1.618）。黄金比例被证明能带来更舒适、更自然的视觉体验，因此 Fibonacci 广泛用于：建筑视觉艺术摄影构图产品工业设计平面设计UI/UX 交互数据可视化大屏展示布局二、为什么 Fibonacci 在 UI 设计中这么重要？✔ 1. 它让界面布局更自然界面中的模块大小、边距、间距采用 Fibonacci 比例，可以呈现：自然层次清晰的重点舒适的视觉流不死板，不平均，不僵硬。✔ 2. 信息更易被用户吸收因为黄金比例符合人类视觉习惯，信息结构按 Fibonacci 排布会让用户感到“顺眼、舒服、有秩序”。✔ 3. 在复杂界面中形成和谐大屏、仪表盘、后台系统往往内容多、层级多。Fibonacci 的不均衡关系（1→1→2→3→5）能让复杂信息变得有序。✔ 4. 自动形成信息权重大模块 = 5次模块 = 3辅助信息 = 2小标签、状态 = 1标注、徽章 = 1这就是天然的信息架构。三、Fibonacci 在 UI 设计中的 6 大应用场景⭐ 1. 布局（Layout）使用 Fibonacci 作为布局比例，例如：主区域：5 份次区域：3 份图表区：2 份数据卡片：1 份高亮警告：1 份示例布局（大屏）：[ 5 ] 主展示（地图 / 网络拓扑 / 运营总览） [ 3 ] 次展示（告警 / 趋势图） [ 2 ] 辅助信息（指标卡片） [ 1 ] 高亮（KPI） [ 1 ] 功能区域（过滤器 / 时间选择）⭐ 2. 网格系统（Grid System）将 UI 网格按 8/13/21/34 划分，而不是平均等分，会让界面更加动态、有韵律。⭐ 3. 字体层级（Typography）Fibonacci 用于字体大小体系非常自然：12 → 14 → 16 → 20 → 24 → 32 → 48这是完美的标题层级体系（非常适合大屏标题）。⭐ 4. 容器与卡片大小（Card / Widget）卡片尺寸建议按 Fibonacci 增长：144px233px377px610px…这种大小之间的“跃迁感”明显，但又统一。⭐ 5. 间距（Spacing）间距系统可以这样设计：4, 6, 10, 16, 26, 42 （≈Fibonacci）比传统 “4/8/12/16” 更有韵律和节奏。⭐ 6. 配色（Color Harmony）这是你最关心的。用 Fibonacci 做配色分成三个方向：（1）颜色数量1 → 极简 3 → 标准系统 5 → UI 完整体系 8 → 大屏图表体系（2）颜色面积比例5 : 3 : 2 : 1 : 1背景：5（50%）主色：3辅助：2强调：1点缀：1（3）HSL 色相偏移主色 H：H + 30° × (1, 2, 3, 5)这就是黄金色相分布。四、Fibonacci 在大屏系统中的实战应用以你正在设计的：综合资源大屏物理层逻辑层用户层运维业务我们可以这样应用 Fibonacci：1. 布局将主区域占比设为 5（40%~55%），次区域 3（20%~30%）。小组件分别是 2、1、1。2. 字体体系H1：48H2：32H3：24H4：20正文：16辅助：14 / 12完美从 Fibonacci 推导。3. 色板体系每个大屏 5 色：背景（5）主色（3）辅助（2）强调（1）点缀（1）这就是你前面用的“黄金配色法”。4. 图表层级大屏图表线条粗细、透明度、颜色权重用 Fibonacci 来体现“信息权重”。五、Fibonacci UI 设计黄金口诀（适合内部规范里写）**布局用 5:3:2:1:1字体用 12→14→16→20→24→32→48配色用 5 色体系色相用 +30°×(1/2/3/5)间距用 Fibonacci 间距**—— UI 自然统一，不生硬、不突兀。六、总结（可直接放在 PPT / 文档结尾）Fibonacci 带来的不仅是“比例好看”，而是：视觉节奏信息层级可读性增强界面统一性降低设计复杂度在大屏、仪表盘、后台等复杂系统中，Fibonacci 几乎是最快速、最高效的设计系统构建方式。斐波那契（Fibonacci）——意大利数学家介绍🇮🇹 一、斐波那契是谁？斐波那契（Fibonacci） 是意大利中世纪最重要的数学家之一，本名：Leonardo Pisano Bigollo（意为“比萨的莱昂纳多”）现代人叫他“Fibonacci”，来自拉丁语 filius Bonacci（邦纳奇之子） 的意译。出生：约 1170 年逝世：约 1240 年 – 1250 年国籍：意大利职业：数学家、学者活动地：比萨共和国（现在意大利托斯卡纳）他被称为 “中世纪欧洲最伟大的数学家”，主要贡献是把阿拉伯数学体系带入欧洲，改变了整个西方的数学发展。📘 二、他的重大贡献1. 把“印度-阿拉伯数字系统”带到欧洲在斐波那契之前，欧洲普遍使用 罗马数字，计算困难且效率极低。斐波那契在他的著作《自由算盘书》（Liber Abaci，1202）中，第一次系统地介绍：0–9 的数字体系位值制四则运算商业计算法（货币、利息、重量换算等）这部书对欧洲数学、商业、银行体系影响巨大。📌 可以说，是斐波那契让欧洲开始用上了“1234567890”。2. 发现并推广了“斐波那契数列”在《Liber Abaci》的一道“兔子繁殖问题”中，他提出：1, 1, 2, 3, 5, 8, 13, …这个数列后来被数学家命名为：Fibonacci Sequence（斐波那契数列）其比值逼近著名的：黄金比例（0.618 / 1.618）如今它广泛应用于：自然科学（植物螺旋、海螺、向日葵）建筑艺术UI/UX 设计（布局、配色、间距）金融技术分析算法设计（动态规划、堆结构）3. 推动了数学习惯从“算盘式”向“代数式”转变斐波那契的工作极大促进了：代数思想的引入阿拉伯数学方法在欧洲的普及商业数学（如利息、换算）在欧洲的规范化欧洲从此进入 数学科学化 的时代。🌍 三、斐波那契的世界影响✔ 1. 是“现代欧洲数学”奠基人他把东方的先进数学带入西方。✔ 2. 是“黄金比例美学”的数学根源今天 UI 设计用的黄金比例、Fibonacci 配色、Fibonacci 布局，都源自他的研究。✔ 3. 被公认为：“中世纪与文艺复兴之间最重要的数学桥梁人物”📜 四、其主要著作书名年份内容Liber Abaci（自由算盘书）1202介绍阿拉伯数字、商数运算、利息与货币换算，含著名兔子模型Practica Geometriae（几何实践）1220几何学著作，含三角、比例、面积计算Liber Quadratorum（平方数之书）1225研究平方数、数论问题Flos（花）1225解三次方程、数学竞赛问题🧩 五、关于他名字的小知识“Fibonacci” 并不是他的真实姓氏是 19 世纪学者 Guilliaume Libri 才给他取的外号意思是 “Bonacci 家族的儿子”所以英文中称他为：FibonacciLeonardo FibonacciLeonardo of Pisa🏆 六、总结**斐波那契是把阿拉伯数字带入欧洲的人，他所提出的斐波那契数列与黄金比例影响了数学、艺术、建筑、设计、投资以及现代 UI 设计。他是中世纪最伟大的数学家之一，也是我们今天设计体系的无形导师。**
AI摘要 成都新公园天府艺术公园占地418万平方米，位于成都老西门，旨在再现“蜀风揽胜画卷”。公园内包含天府艺术馆、天府美术馆和天府图书馆，其中美术馆屋顶设计为芙蓉花瓣形状，艺术馆和图书馆屋顶为斜向上的斜面曲面。公园预计将于2021年8月前开放，有望成为成都新地标。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 初次对成都加深印象是在什么时候呢？应该是那句歌词唱的那样，走在成都的街道上也停不下来，这首歌火遍了校园，到处都能听到哼着小曲的人。成都的这个热闹的大城市，一个又一个远方的游客的吸引着人们，成都的发展观光业也在这里盛行，父亲总是一种令人羡慕的语气，他的旧同学谁又说成都旅行了，这样，这里真的是一个好地方，这个公园的名字叫天府艺术公园，地点在成都老西门，这个公园的建成范围有多大？这个公园的目的是再现“蜀风揽胜画卷”，既然是艺术类的，那一定展示了艺术性的内容，这个公园还引进了展馆。有天府艺术馆、天府美术馆、天府图书馆，下面详细说明这些展馆。天府美术馆，这个展馆富有艺术性，屋顶是用芙蓉花瓣形状的金属素材建造的，从远处看，这个美术馆是盛开在湖里的芙蓉花。天府艺术馆和图书馆的屋顶造型是斜向上的斜面曲面，对面就是美术馆，今后，如果小队友们如果有读书的欲望，在图书馆中读书，读着书，同时可以欣赏美丽的设计，精神的喜悦心情，同时也能好转，这是什么人的人梦想的生活！该公园的建设是一个文化艺术爱好者争相，但他们在图书馆里徜徉中，美术馆也培养情操，比他们两人呐喊，也在一个地方停留的时间长了，特别是也改变了，从任何角度而言的一种心情，这是文艺爱好者的理想的天堂，今后，这里很有可能成为成都的“新地标”，对这里感兴趣的朋友们不要着急，这个公园将在2021年8月之前亮相。
AI摘要 成都轨道交通5条新线6项目车站集体亮相，各线路车站设计融入沿线特色，展现天府文化。6号线以“科技华章、创新菁蓉”为主题，8号线以“音乐地铁，国际文创”为主题，9号线以“锦绣芳华，绿裳画图”为主题，17号线以“水育柳城，丝织锦绣”为主题，18号线福田站以航空器造型和机舱设计为主题，各线路车站设计展现成都世界文化艺术名城气息。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 就在今天！！！备受关注的5线6项目车站集体“闪亮登场”！5条新线串联成都多个区域，站厅设计也囊括了科技华章、音乐地铁、锦绣芳华、水育柳城等多元风格，将沿线特色融入厅站风貌，用轨道交通串联天府文化！6号线一二三期主题：“科技华章、创新菁蓉”6号线根据站点周边文化定位全线站点，共设两个层级，其中一般艺术站45座（不含西博城站和西北桥站），重点艺术站9座。9座重点艺术站望丛祠站、金府站、西南交大站、建设北路站、牛王庙站、三官堂站、金融城东站、秦皇寺站、天府商务区站。01望丛祠站以“望丛二帝”为主题，灵感源自都江堰的地势水形及成都平原综合交错的自然形态，通过深色的天花格栅和不同层次的绿色片状金属板以表达自然形态中“水与田”的元素。02金府站以“青春活力”为主题，灵感源于紧邻的欢乐谷过山车等游乐设施的曲线造型，通过优美的曲线造型和流光溢彩的色带，表达欢快和动感的游乐气氛。运用鲜明的色彩对比，展现出一个梦幻的世界。03西南交大站以“天堑变通途”为主题，提炼了西南交大象征性的桥梁、隧道、大门的拱形元素作为空间构成的基础要素，以表达“传承、纪念、学术、探索”的主旨；车站艺术品名为“天堑变通途”。04建设北路站以“时光穿梭”为主题，营造科技、未来的空间气质，通过深色的天花背景和线性格栅、灯具，形成明暗对比强烈的震撼的视觉感受，整个地铁站成为链接川蜀工业过去与未来的“时空虫洞”。05牛王庙站以“市井牛王庙”为主题，从老的历史街区场景中提取元素，经过现代手法的演绎，形成一种穿越时空、传统与现代再次对话的独特空间，使人们再次体验到昔日散发浓厚酒文化的市井场景，感受成都老街巷的独特魅力。06三官堂站以“成都工业文明”为主题，提取自四川机械总局旧址建筑的元素，通过回应拱券的造型，使用木纹格栅和混凝土饰面材料，展示出“工业、高大，张力、厚重”的历史感，经过现代手法的演绎，形成一种穿越时空、传统与现代再次对话的独特空间。07金融城东站以“科技金融创新”为主题，通过对可视化的“金融数据”及代表科技核心的“芯片电路”提取动感、强劲的线性元素，并以白色金属为基底，金、银作为点缀性主题色，赋予金融城东站时尚、科技的空间特征。08天府商务区站以“科技创新·AI智能”为主题，提取“人工智能虚拟大脑”的三角形元素，通过重复连续的设计手法，将整个车站空间联系起来，塑造出形式统一并富有变化的多维空间。09秦皇寺站以“科技创新·互联共享”为主题，将抽象的“互联网”、具象的“川西竹编”两种元素相融合，这是刚与柔的碰撞，形与光的契合。体现科技与传统的交织，传达出互联网共享与文化传承欣欣向荣的发展意向。45座一般艺术站6号线一二期工程一般艺术站根据区位，共划分为三个段落，从望丛祠站到西华大道站，为“先进制造业集中、复合型产学研新城”的高新西区展现出“青春创业活力”的文化个性；从金府站到琉三路站，为“历史悠久、文化商业活力强盛”的主城区段展现出“历史科技华章”的人文情怀；从金石路站到观东站，为“现代商务中心、高端产业新城”的高新南区能展现出“时代科技创新”的艺术格调。三期工程整体空间着重于功能性、舒适性，采用空间一体化打造手法，通过将天花、墙面相连，统一设计打造，使整个车站空间浑然一体，塑造出具有“科技感”的空间氛围。8号线一期主题：“音乐地铁，国际文创”8号线一期所经区域覆盖众多教育、音乐及文创产业，故将“音乐地铁，国际文创”作为本线路设计主题，以文创方式多维表现音乐主题的地铁空间，个性鲜明，独具特色。5座重点艺术站川大江安校区站、芳草街站、川大望江校区站、东郊记忆站、理工大学站。01川大江安校区站以“风华正茂”为设计主题，将展开的“书本”作为空间延展的主体装饰构造，“书页”片状的组合形式活泼、清逸，通过冰蓝色灯光的渲染，形象的展现川大新校区现代、朝气的精神风貌和活力。02芳草街站以“玉林百态”为设计主题，提取玉林片区街景风貌的特点，以绿色为基调，通过对金属材料的镂空处理，将空间营造出树影婆娑的质感，空间极富张力，宁静而惬意，与玉林片区的生活及人文内涵相得益彰，将为乘客提供别样的车站空间体验。03川大望江校区站以“人文百年”为设计主题，从校区建筑群落中寻找历史印记，对空间形式、质感、色彩进行一定程度的还原表达。穹顶形式的应用，书卷展开的风雅，整体空间沉稳、庄重，各个细节共同勾勒出川大百年厚重的历史沉淀与精神内涵。04东郊记忆站以“工业新音”为设计主题，空间运用充满力量感的“工业化”形象色彩、肌理进行打造，构成方式主要为线性表现，并结合充满律动的灯光、生动的多媒体艺术品表现，让乘客穿梭于时间与光影的空间氛围里，工业的辉煌未有失色，更替的是对文化创造的无限热情。05理工大学站以“蜀地绝响”为设计主题，将理工大学地质、能源等教育科研领域的特色作为创作出发点，以风蚀地貌的概念进行空间构筑，日月星辰下蜿蜒曲折的空间形态浑然天成，换乘区域通过富有象征性的“生物化石”天花构成进行过渡，强调空间聚合力和次序感。20座标准艺术站通过音乐文化因素和站域特点的相互叠加，形成两个分段线路文化篇章，分别是“西洋律动”“传统国韵”。通过两个篇章的文化点、线表达，更加立体的呈现了以音乐作为主题打造的8号线一期独有的文化气质和内涵。9号线一期主题：“锦绣芳华，绿裳画图”9号线一期工程将城市的优雅之美和古典之美以空间一体化的设计手法进行打造，以蜀川胜概为底图，用蜀锦蜀绣的编织纹路演绎，描绘成都山、水、田、林、城构筑的公园城市，寻找成都的繁华与秀丽，领略这座城市独有的风姿，感悟博大精深源远流长的天府文化。4座重点艺术站孵化园站、锦城大道站、簇桥站和机投桥站。01孵化园站以“科技创新”为设计主题，用“折线”三维元素将空间、灯具、综合末端一体化打造，创造具有科技未来感、同时又契合“创业天府”文化定位的艺术空间。整体造型嵌入多媒体艺术装置，增加趣味性和互动性，展现科技无限的遐想，实现艺术空间一体化。02锦城大道站以“锦绣蓉城”为设计主题，提取市花“芙蓉”，运用到柱子进行一体化设计，柱子之间形成一条贯穿站厅的廊道，营造生态自然的氛围，将成都的生态文化融入站点设计,体现成都人民休闲生活慢行的状态。03簇桥站设计灵感来自传统的“养蚕缫丝”场景，将“蚕茧”“蚕簇”和“生丝”的故事线融入车站空间之中。墙柱面造型融合桑树形态，进行空间一体化设计，艺术灯具表达蚕茧连着蚕丝缠绕桑树交错的画面，灵动的展现了“养蚕缫丝”的画面感。04机投桥站以“织锦文化”为设计主题，以织锦垂挂的自然形态为造型元素，搭配织锦的华丽色彩，模拟蜀锦织造基地织布垂挂的场景，将织造的美感大胆与传统建筑进行拼接，形成独特的时空对话，体现了“蜀锦历史名镇”的风采，展现了锦绣芳华的工艺之美。9座一般艺术站分别为金融城东站、心岛站、三元站、太平寺站、华兴站、武青南站、培风站、成都西站、黄田坝站。以“巴蜀锦绣、魂之凝结”为基石对蜀锦蜀绣文化最具代表图案“芙蓉”进行结构提炼，重组转化为抽象的元素在空间进行一体化表达,使整个车站形成一个富有独特文化气质的空间。同时，在局部巧妙地将蜀锦蜀绣形态融入地域文化，用蜀锦蜀绣的编织纹路展现了川西林盘、秀丽锦江之美。17号线一期主题：“水育柳城，丝织锦绣”17号线一期工程以“水润天府，生态成都”和“浮光掠影，记忆成都”为设计理念。在生态线路段中提取自然的秀丽形态与历史的文昌古韵，表达天府之都在水文化孕育下的生态与健康，通过河流的形式变化与自然形态的提取元素贯穿整条线路。2座重点艺术站凤溪河站和机投桥站。01凤溪河站以水为引，以生态为线，利用站内层高的优势，打造出一个视野宽广的空间。天花以流动的水为元素，从天花衍生到柱面，使顶面与立面贯通，如大河之水顺流而下。凤溪河站艺术品展现了逾三千年的古蜀国历史悠久，其灿烂的文明在华夏历史独树一帜。02机投桥站机投桥站以“花楼织锦”作为设计理念，将传统蜀锦织布机和传统建筑作为空间的创作起点，天花流畅的线条结合人字坡传统形态，仿佛布匹织锦的韵律贯穿整体空间，并在空间局部延展至墙面，承载此站点的文化主题，合力发扬传统工艺的工匠魅力，延续历史空间记忆。7个标准站分别为金星站、黄石站、市五医院站、温泉大道站、明光站、九江北站、白佛桥站，按位置分为“水历史”“水生态”“成都记忆”三个区段，设计思路上延续了以“水”元素为主导，并提取绿蓝黄三个色系串联整条线路的标准站概念主题。站厅装修以白色调为主，从端墙两侧倒角弧板，通过一层一层的圆通弧线穿插。将水的韵律从两侧向中间流动。18号线：福田站福田高架站外立面整体以前卫的航空器造型，辅以极具速度感的条窗呈现一幅翱翔腾飞的姿态，预示着空港新区繁荣发展的美好愿景。车站内部全段以机舱为设计主题，经由机舱窗口的造型，延伸出透过窗口看天府的设计概念。成都轨道交通将坚持以“璀璨巴蜀，活力天府”为主旨，以“世界级的地下文化走廊、国际范的天府文化名片、蜀都韵的流动博物馆”为目标，按照一线多景的设计思路，彰显成都世界文化艺术名城的气息。
AI摘要 本文介绍了CSS中常用的长度单位px、em、rem、rpx和upx。px是像素单位，与显示器分辨率相关；em是相对长度单位，相对于当前对象内文本的字体尺寸；rem是CSS3新增的单位，相对于HTML根元素；rpx是微信小程序的尺寸单位，根据屏幕宽度自适应；upx是uni-app的尺寸单位，也是根据屏幕宽度自适应。选择合适的单位取决于项目需求和浏览器兼容性。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); PXpx像素（Pixel）。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。PX特点IE无法调整那些使用px作为单位的字体大小；国外的大部分网站能够调整的原因在于其使用了em或rem作为字体单位；Firefox能够调整px和em，rem，但是96%以上的中国网民使用IE浏览器(或内核)。EMem是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置，则相对于浏览器的默认字体尺寸。EM特点em的值并不是固定的；em会继承父级元素的字体大小。注意：任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1em=16px。那么12px=0.75em,10px=0.625em。为了简化font-size的换算，需要在css中的body选择器中声明Font-size=62.5%，这就使em值变为 16px*62.5%=10px, 这样12px=1.2em, 10px=1em, 也就是说只需要将你的原来的px数值除以10，然后换上em作为单位就行了。所以我们在写CSS的时候，需要注意两点：body选择器中声明Font-size=62.5%；将你的原来的px数值除以10，然后换上em作为单位；重新计算那些被放大的字体的em数值。避免字体大小的重复声明。也就是避免1.2 * 1.2= 1.44的现象。比如说你在#content中声明了字体大小为1.2em，那么在声明p的字体大小时就只能是1em，而不是1.2em, 因为此em非彼em，它因继承#content的字体高而变为了1em=12px。REMrem是CSS3新增的一个相对单位（root em，根em），这个单位引起了广泛关注。这个单位与em有什么区别呢？区别在于使用rem为元素设定字体大小时，仍然是相对大小，但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身，通过它既可以做到只修改根元素就成比例地调整所有字体大小，又可以避免字体大小逐层复合的连锁反应。目前，除了IE8及更早版本外，所有浏览器均已支持rem。对于不支持它的浏览器，应对方法也很简单，就是多写一个绝对单位的声明。这些浏览器会忽略用rem设定的字体大小。下面就是一个例子：p {font-size:14px; font-size:.875rem;}注意： 选择使用什么字体单位主要由你的项目来决定，如果你的用户群都使用最新版的浏览器，那推荐使用rem，如果要考虑兼容性，那就使用px,或者两者同时使用。px 与 rem 的选择？对于只需要适配少部分手机设备，且分辨率对页面影响不大的，使用px即可 。对于需要适配各种移动设备，使用rem，例如只需要适配iPhone和iPad等分辨率差别比较挺大的设备。rpxrpx 是微信小程序解决自适应屏幕尺寸的尺寸单位。微信小程序规定屏幕的宽度为750rpx。无论是在iPhone6上面还是其他机型上面都是750rpx的屏幕宽度，拿iPhone6来讲，屏幕宽度为375px，把它分为750rpx后， 1rpx = 0.5px。微信小程序同时也支持rem尺寸单位， rem 规定屏幕的宽度为20rem, 所以 1rem = (750/20)rpx = 37.5 rpxUPXuni-app 使用 upx 作为默认尺寸单位， upx 是相对于基准宽度的单位，可以根据屏幕宽度进行自适应。uni-app 规定屏幕基准宽度750upx。开发者可以通过设计稿基准宽度计算页面元素 upx 值，设计稿 1px 与框架样式 1upx 转换公式如下：设计稿 1px / 设计稿基准宽度 = 框架样式 1upx / 750upxupx在2018年就推出了，其初衷是为了让微信的rpx机制可以跨全端使用。在普通css写法里，upx会被编译器编译。但动态绑定时，upx无法被准确编译，此时官方提出了使用uni.upx2px()方法，用来动态计算。
AI摘要 本文是一篇关于Golang汇编入门的知识总结，作者分享了自己在学习Golang底层实现机制时对Golang汇编的理解。文章首先解释了为什么需要汇编语言，然后介绍了程序与进程的概念，接着深入讲解了Go汇编的相关知识，包括核心寄存器、常用操作指令等。文章还提供了如何输出Go汇编的方法，并通过对一个简单Go函数的汇编代码进行分析，帮助读者理解Go汇编的实现过程。最后，文章还介绍了一些Go调试工具，如gdb和delve，并总结了学习Go汇编需要熟悉的几个方面。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 作者：ivansli，腾讯 IEG 运营开发工程师在深入学习 Golang 的 runtime 和标准库实现的时候发现，如果对 Golang 汇编没有一定了解的话，很难深入了解其底层实现机制。在这里整理总结了一份基础的 Golang 汇编入门知识，通过学习之后能够对其底层实现有一定的认识。0. 为什么写本文平时业务中一直使用 PHP 编写代码，但是一直对 Golang 比较感兴趣，闲暇、周末之余会看一些 Go 底层源码。近日在分析 go 的某些特性底层功能实现时发现：有些又跟 runtime 运行时有关，而要掌握这一部分的话，有一道坎是绕不过去的，那就是 Go 汇编。索性就查阅了很多大佬们写的资料，在阅读之余整理总结了一下，并在这里分享给大家。本文使用 Go 版本为 go1.14.11. 为什么需要汇编众所周知，在计算机的世界里，只有 2 种类型。那就是：0 和 1。计算机工作是由一系列的机器指令进行驱动的，这些指令又是一组二进制数字，其对应计算机的高低电平。而这些机器指令的集合就是机器语言，这些机器语言在最底层是与硬件一一对应的。显而易见，这样的机器指令有一个致命的缺点：可阅读性太差（恐怕也只有天才和疯子才有能力把控得了）。为了解决可读性的问题以及代码编辑的需求，于是就诞生了最接近机器的语言：汇编语言（在我看来，汇编语言更像一种助记符，这些人们容易记住的每一条助记符都映射着一条不容易记住的由 0、1 组成的机器指令。你觉得像不像域名与 IP 地址的关系呢？）。1.1 程序的编译过程以 C 语言为例来说，从 hello.c 的源码文件到 hello 可执行文件，经过编译器处理，大致分为几个阶段：编译器在不同的阶段会做不同的事情，但是有一步是可以确定的，那就是：源码会被编译成汇编，最后才是二进制。2. 程序与进程源码经过编译之后，得到一个二进制的可执行 文件。文件这两个字也就表明，目前得到的这个文件跟其他文件对比，除了是具有一定的格式（Linux 中是 ELF 格式，即：可运行可链接。executable linkable formate）的二进制组成，并没什么区别。在 Linux 中文件类型大致分为 7 种：b: 块设备文件c：字符设备文件d：目录-：普通文件l：链接s：socketp：管道通过上面可以看到，可执行文件 main 与源码文件 main.go，都是同一种类型，属于普通文件。（当然了，在 Unix 中有一句很经典的话：一切皆文件）。那么，问题来了：什么是程序？什么是进程？2.1 程序维基百科告诉我们：程序是指一组指示计算机或其他具有消息处理能力设备每一步动作的指令，通常用某种程序设计语言编写，运行于某种目标体系结构上。从某个层面来看，可以把程序分为静态程序、动态程序：静态程序：单纯的指具有一定格式的可执行二进制文件。动态程序：则是静态可执行程序文件被加载到内存之后的一种运行时模型（又称为进程）。2.2 进程首先，要知道的是，进程是分配系统资源的最小单位，线程(带有时间片的函数)是系统调度的最小单位。进程包含线程，线程所属于进程。创建进程一般使用 fork 方法(通常会有个拉起程序，先 fork 自身生成一个子进程。然后，在该子进程中通过 exec 函数把对应程序加载进来，进而启动目标进程。当然，实际上要复杂得多)，而创建线程则是使用 pthread 线程库。以 32 位 Linux 操作系统为例，进程经典的虚拟内存结构模型如下图所示：其中，有两处结构是静态程序所不具有的，那就是 运行时堆(heap)与 运行时栈(stack)。运行时堆从低地址向高地址增长，申请的内存空间需要程序员自己或者由 GC 释放。运行时栈从高地址向低地址增长，内存空间在当前栈桢调用结束之后自动释放(并不是清除其所占用内存中数据，而是通过栈顶指针 SP 的移动，来标识哪些内存是正在使用的)。3. Go 汇编对于 Go 编译器而言，其输出的结果是一种抽象可移植的汇编代码，这种汇编（Go 的汇编是基于 Plan9 的汇编）并不对应某种真实的硬件架构。Go 的汇编器会使用这种伪汇编，再为目标硬件生成具体的机器指令。伪汇编这一个额外层可以带来很多好处，最主要的一点是方便将 Go 移植到新的架构上。相关的信息可以参考 Rob Pike 的 The Design of the Go Assembler。要了解 Go 的汇编器最重要的是要知道 Go 的汇编器不是对底层机器的直接表示，即 Go 的汇编器没有直接使用目标机器的汇编指令。Go 汇编器所用的指令，一部分与目标机器的指令一一对应，而另外一部分则不是。这是因为编译器套件不需要汇编器直接参与常规的编译过程。相反，编译器使用了一种半抽象的指令集，并且部分指令是在代码生成后才被选择的。汇编器基于这种半抽象的形式工作，所以虽然你看到的是一条 MOV 指令，但是工具链针对对这条指令实际生成可能完全不是一个移动指令，也许会是清除或者加载。也有可能精确的对应目标平台上同名的指令。概括来说，特定于机器的指令会以他们的本尊出现， 然而对于一些通用的操作，如内存的移动以及子程序的调用以及返回通常都做了抽象。细节因架构不同而不一样，我们对这样的不精确性表示歉意，情况并不明确。汇编器程序的工作是对这样半抽象指令集进行解析并将其转变为可以输入到链接器的指令。The most important thing to know about Go’s assembler is that it is not a direct representation of the underlying machine. Some of the details map precisely to the machine, but some do not. This is because the compiler suite needs no assembler pass in the usual pipeline. Instead, the compiler operates on a kind of semi-abstract instruction set, and instruction selection occurs partly after code generation. The assembler works on the semi-abstract form, so when you see an instruction like MOV what the toolchain actually generates for that operation might not be a move instruction at all, perhaps a clear or load.Or it might correspond exactly to the machine instruction with that name. In general, machine-specific operations tend to appear as themselves, while more general concepts like memory move and subroutine call and return are more abstract. The details vary with architecture, and we apologize for the imprecision; the situation is not well-defined.The assembler program is a way to parse a description of that semi-abstract instruction set and turn it into instructions to be input to the linker.Go 汇编使用的是 caller-save模式，被调用函数的入参参数、返回值都由调用者维护、准备。因此，当需要调用一个函数时，需要先将这些工作准备好，才调用下一个函数，另外这些都需要进行内存对齐，对齐的大小是 sizeof(uintptr)。3.1 几个概念在深入了解 Go 汇编之前，需要知道的几个概念：栈：进程、线程、goroutine 都有自己的调用栈，先进后出（FILO）栈帧：可以理解是函数调用时，在栈上为函数所分配的内存区域调用者：caller，比如：A 函数调用了 B 函数，那么 A 就是调用者被调者：callee，比如：A 函数调用了 B 函数，那么 B 就是被调者3.2 Go 的核心寄存器go 汇编中有 4 个核心的伪寄存器，这 4 个寄存器是编译器用来维护上下文、特殊标识等作用的：寄存器说明SB(Static base pointer)global symbolsFP(Frame pointer)arguments and localsPC(Program counter)jumps and branchesSP(Stack pointer)top of stackFP: 使用如symbol+offset(FP)的方式，引用 callee 函数的入参参数。例如arg0+0(FP)，arg1+8(FP)，使用 FP 必须加 symbol ，否则无法通过编译(从汇编层面来看，symbol 没有什么用，加 symbol 主要是为了提升代码可读性)。另外，需要注意的是：往往在编写 go 汇编代码时，要站在 callee 的角度来看(FP)，在 callee 看来，(FP)指向的是 caller 调用 callee 时传递的第一个参数的位置。假如当前的 callee 函数是 add，在 add 的代码中引用 FP，该 FP 指向的位置不在 callee 的 stack frame 之内。而是在 caller 的 stack frame 上，指向调用 add 函数时传递的第一个参数的位置，经常在 callee 中用symbol+offset(FP)来获取入参的参数值。SB: 全局静态基指针，一般用在声明函数、全局变量中。SP: 该寄存器也是最具有迷惑性的寄存器，因为会有伪 SP 寄存器和硬件 SP 寄存器之分。plan9 的这个伪 SP 寄存器指向当前栈帧第一个局部变量的结束位置(为什么说是结束位置，可以看下面寄存器内存布局图)，使用形如 symbol+offset(SP) 的方式，引用函数的局部变量。offset 的合法取值是 [-framesize, 0)，注意是个左闭右开的区间。假如局部变量都是 8 字节，那么第一个局部变量就可以用 localvar0-8(SP) 来表示。与硬件寄存器 SP 是两个不同的东西，在栈帧 size 为 0 的情况下，伪寄存器 SP 和硬件寄存器 SP 指向同一位置。手写汇编代码时，如果是 symbol+offset(SP)形式，则表示伪寄存器 SP。如果是 offset(SP)则表示硬件寄存器 SP。务必注意：对于编译输出(go tool compile -S / go tool objdump)的代码来讲，所有的 SP 都是硬件 SP 寄存器，无论是否带 symbol（这一点非常具有迷惑性，需要慢慢理解。往往在分析编译输出的汇编时，看到的就是硬件 SP 寄存器）。PC: 实际上就是在体系结构的知识中常见的 pc 寄存器，在 x86 平台下对应 ip 寄存器，amd64 上则是 rip。除了个别跳转之外，手写 plan9 汇编代码时，很少用到 PC 寄存器。通过上面的讲解，想必已经对 4 个核心寄存器的区别有了一定的认识（或者是更加的迷惑、一头雾水）。那么，需要留意的是：如果是在分析编译输出的汇编代码时，要重点看 SP、SB 寄存器（FP 寄存器在这里是看不到的）。如果是，在手写汇编代码，那么要重点看 FP、SP 寄存器。3.2.1 伪寄存器的内存模型下图描述了栈桢与各个寄存器的内存关系模型，值得注意的是要站在 callee 的角度来看。有一点需要注意的是，return addr 也是在 caller 的栈上的，不过往栈上插 return addr 的过程是由 CALL 指令完成的（在分析汇编时，是看不到关于 addr 相关空间信息的。在分配栈空间时，addr 所占用空间大小不包含在栈帧大小内）。在 AMD64 环境，伪 PC 寄存器其实是 IP 指令计数器寄存器的别名。伪 FP 寄存器对应的是 caller 函数的帧指针，一般用来访问 callee 函数的入参参数和返回值。伪 SP 栈指针对应的是当前 callee 函数栈帧的底部（不包括参数和返回值部分），一般用于定位局部变量。伪 SP 是一个比较特殊的寄存器，因为还存在一个同名的 SP 真寄存器，真 SP 寄存器对应的是栈的顶部。在编写 Go 汇编时，当需要区分伪寄存器和真寄存器的时候只需要记住一点：伪寄存器一般需要一个标识符和偏移量为前缀，如果没有标识符前缀则是真寄存器。比如(SP)、+8(SP)没有标识符前缀为真 SP 寄存器，而 a(SP)、b+8(SP)有标识符为前缀表示伪寄存器。3.2.2 几点说明我们这里对容易混淆的几点简单进行说明：伪 SP 和硬件 SP 不是一回事，在手写汇编代码时，伪 SP 和硬件 SP 的区分方法是看该 SP 前是否有 symbol。如果有 symbol，那么即为伪寄存器，如果没有，那么说明是硬件 SP 寄存器。伪 SP 和 FP 的相对位置是会变的，所以不应该尝试用伪 SP 寄存器去找那些用 FP+offset 来引用的值，例如函数的入参和返回值。官方文档中说的伪 SP 指向 stack 的 top，可能是有问题的。其指向的局部变量位置实际上是整个栈的栈底（除 caller BP 之外），所以说 bottom 更合适一些。在 go tool objdump/go tool compile -S 输出的代码中，是没有伪 SP 和 FP 寄存器的，我们上面说的区分伪 SP 和硬件 SP 寄存器的方法，对于上述两个命令的输出结果是没法使用的。在编译和反汇编的结果中，只有真实的 SP 寄存器。3.2.3 IA64 和 plan9 的对应关系在 plan9 汇编里还可以直接使用的 amd64 的通用寄存器，应用代码层面会用到的通用寄存器主要是: rax, rbx, rcx, rdx, rdi, rsi, r8~r15 这些寄存器，虽然 rbp 和 rsp 也可以用，不过 bp 和 sp 会被用来管理栈顶和栈底，最好不要拿来进行运算。plan9 中使用寄存器不需要带 r 或 e 的前缀，例如 rax，只要写 AX 即可: MOVQ $101, AX = mov rax, 101下面是通用通用寄存器的名字在 IA64 和 plan9 中的对应关系:3.3 常用操作指令下面列出了常用的几个汇编指令（指令后缀 Q 说明是 64 位上的汇编指令）助记符指令种类用途示例MOVQ传送数据传送MOVQ 48, AX // 把 48 传送到 AXLEAQ传送地址传送LEAQ AX, BX // 把 AX 有效地址传送到 BXPUSHQ传送栈压入PUSHQ AX // 将 AX 内容送入栈顶位置POPQ传送栈弹出POPQ AX // 弹出栈顶数据后修改栈顶指针ADDQ运算相加并赋值ADDQ BX, AX // 等价于 AX+=BXSUBQ运算相减并赋值SUBQ BX, AX // 等价于 AX-=BXCMPQ运算比较大小CMPQ SI CX // 比较 SI 和 CX 的大小CALL转移调用函数CALL runtime.printnl(SB) // 发起调用JMP转移无条件转移指令JMP 0x0185 //无条件转至 0x0185 地址处JLS转移条件转移指令JLS 0x0185 //左边小于右边，则跳到 0x01854. 汇编分析说了那么多，it is code show time。4.1 如何输出 Go 汇编对于写好的 go 源码，生成对应的 Go 汇编，大概有下面几种方法 1 先使用go build -gcflags "-N -l" main.go 生成对应的可执行二进制文件 再使用go tool objdump -s "main\." main 反编译获取对应的汇编反编译时 "main\." 表示只输出 main 包中相关的汇编 "main\.main" 则表示只输出 main 包中 main 方法相关的汇编方法 2 使用go tool compile -S -N -l main.go 这种方式直接输出汇编方法 3 使用go build -gcflags="-N -l -S" main.go 直接输出汇编注意：在使用这些命令时，加上对应的 flag，否则某些逻辑会被编译器优化掉，而看不到对应完整的汇编代码-l 禁止内联 -N 编译时，禁止优化 -S 输出汇编代码4.2 Go 汇编示例go 示例代码package main func add(a, b int) int{ sum := 0 // 不设置该局部变量sum，add栈空间大小会是0 sum = a+b return sum} func main(){ println(add(1,2))}编译 go 源代码，输出汇编go tool compile -N -l -S main.go截取主要汇编如下："".add STEXT nosplit size=60 args=0x18 locals=0x10 0x0000 00000 (main.go:3) TEXT "".add(SB), NOSPLIT, $16-24 0x0000 00000 (main.go:3) SUBQ $16, SP ;;生成add栈空间 0x0004 00004 (main.go:3) MOVQ BP, 8(SP) 0x0009 00009 (main.go:3) LEAQ 8(SP), BP ;; ...omitted FUNCDATA stuff... 0x000e 00014 (main.go:3) MOVQ $0, "".~r2+40(SP) ;;初始化返回值 0x0017 00023 (main.go:4) MOVQ $0, "".sum(SP) ;;局部变量sum赋为0 0x001f 00031 (main.go:5) MOVQ "".a+24(SP), AX ;;取参数a 0x0024 00036 (main.go:5) ADDQ "".b+32(SP), AX ;;等价于AX=a+b 0x0029 00041 (main.go:5) MOVQ AX, "".sum(SP) ;;赋值局部变量sum 0x002d 00045 (main.go:6) MOVQ AX, "".~r2+40(SP) ;;设置返回值 0x0032 00050 (main.go:6) MOVQ 8(SP), BP 0x0037 00055 (main.go:6) ADDQ $16, SP ;;清除add栈空间 0x003b 00059 (main.go:6) RET ...... "".main STEXT size=107 args=0x0 locals=0x28 0x0000 00000 (main.go:9) TEXT "".main(SB), $40-0 ...... 0x000f 00015 (main.go:9) SUBQ $40, SP ;; 生成main栈空间 0x0013 00019 (main.go:9) MOVQ BP, 32(SP) 0x0018 00024 (main.go:9) LEAQ 32(SP), BP ;; ...omitted FUNCDATA stuff... 0x001d 00029 (main.go:10) MOVQ $1, (SP) ;;add入参：1 0x0025 00037 (main.go:10) MOVQ $2, 8(SP) ;;add入参：2 0x002e 00046 (main.go:10) CALL "".add(SB) ;;调用add函数 0x0033 00051 (main.go:10) MOVQ 16(SP), AX 0x0038 00056 (main.go:10) MOVQ AX, ""..autotmp_0+24(SP) 0x003d 00061 (main.go:10) CALL runtime.printlock(SB) 0x0042 00066 (main.go:10) MOVQ ""..autotmp_0+24(SP), AX 0x0047 00071 (main.go:10) MOVQ AX, (SP) 0x004b 00075 (main.go:10) CALL runtime.printint(SB) 0x0050 00080 (main.go:10) CALL runtime.printnl(SB) 0x0055 00085 (main.go:10) CALL runtime.printunlock(SB) 0x005a 00090 (main.go:11) MOVQ 32(SP), BP 0x005f 00095 (main.go:11) ADDQ $40, SP ;;清除main栈空间 0x0063 00099 (main.go:11) RET ......这里列举了一个简单的 int 类型 加法示例，实际开发中会遇到各种参数类型，要复杂的多，这里只是抛砖引玉 :)4.3 Go 汇编解析针对 4.2 输出汇编，对重要核心代码进行分析。4.3.1 add 函数汇编解析TEXT "".add(SB), NOSPLIT|ABIInternal, $16-24TEXT "".add TEXT 指令声明了 "".add 是 .text 代码段的一部分，并表明跟在这个声明后的是函数的函数体。在链接期，""这个空字符会被替换为当前的包名: 也就是说，"".add 在链接到二进制文件后会变成 main.add(SB) SB 是一个虚拟的伪寄存器，保存静态基地址(static-base) 指针，即我们程序地址空间的开始地址。"".add(SB) 表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲，它有一个直接的绝对地址: 是一个全局的函数符号。NOSPLIT: 向编译器表明不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下，编译器自己帮我们插入了这个标记: 它足够聪明地意识到，由于 add 没有任何局部变量且没有它自己的栈帧，所以一定不会超出当前的栈。不然，每次调用函数时，在这里执行栈检查就是完全浪费 CPU 时间了。$0-1624 指定了调用方传入的参数+返回值大小（24 字节=入参 a、b 大小 8字节*2+ 返回值8字节）> 通常来讲，帧大小后一般都跟随着一个参数大小，用减号分隔。(这不是一个减法操作，只是一种特殊的语法) 帧大小 $24-8 意味着这个函数有 24 个字节的帧以及 8 个字节的参数，位于调用者的帧上。如果 NOSPLIT 没有在 TEXT 中指定，则必须提供参数大小。对于 Go 原型的汇编函数，go vet 会检查参数大小是否正确。In the general case, the frame size is followed by an argument size, separated by a minus sign. (It’s not a subtraction, just idiosyncratic syntax.) The frame size $24-8 states that the function has a 24-byte frame and is called with 8 bytes of argument, which live on the caller’s frame. If NOSPLIT is not specified for the TEXT, the argument size must be provided. For assembly functions with Go prototypes, go vet will check that the argument size is correct.SUBQ $16, SPSP 为栈顶指针，该语句等价于 SP-=16（由于栈空间是向下增长的，所以开辟栈空间时为减操作），表示生成 16 字节大小的栈空间。MOVQ $0, "".~r2+40(SP)此时的 SP 为 add 函数栈的栈顶指针，40(SP)的位置则是 add 返回值的位置，该位置位于 main 函数栈空间内。该语句设置返回值类型的 0 值，即初始化返回值，防止得到脏数据（返回值类型为 int，int 的 0 值为 0）。MOVQ "".a+24(SP), AX从 main 函数栈空间获取入参 a 的值，存到寄存器 AXADDQ "".b+32(SP), AX从 main 函数栈空间获取入参 b 的值，与寄存器 AX 中存储的 a 值相加，结果存到 AX。相当于 AX=a+bMOVQ AX, "".~r2+40(SP)把 a+b 的结果放到 main 函数栈中, add(a+b)返回值所在的位置ADDQ $16, SP归还 add 函数占用的栈空间4.3.2 函数栈桢结构模型根据 4.2 对应汇编绘制的函数栈桢结构模型还记得前面提到的，Go 汇编使用的是 caller-save模式，被调用函数的参数、返回值、栈位置都需要由调用者维护、准备吗？在函数栈桢结构中可以看到，add()函数的入参以及返回值都由调用者 main()函数维护。也正是因为如此，GO 有了其他语言不具有的，支持多个返回值的特性。4.4 Go 汇编语法这里重点讲一下函数声明、变量声明。4.4.1 函数声明来看一个典型的 Go 汇编函数定义// func add(a, b int) int// 该add函数声明定义在同一个 package name 下的任意 .go文件中// 只有函数头，没有实现 // add函数的Go汇编实现// pkgname 默认是 ""TEXT pkgname·add(SB), NOSPLIT, $16-24 MOVQ a+0(FP), AX ADDQ b+8(FP), AX MOVQ AX, ret+16(FP) RETGo 汇编实现为什么是 TEXT 开头？仔细观察上面的进程内存布局图就会发现，我们的代码在是存储在.text 段中的，这里也就是一种约定俗成的起名方式。实际上在 plan9 中 TEXT 是一个指令，用来定义一个函数。定义中的 pkgname 是可以省略的，(非想写也可以写上，不过写上 pkgname 的话，在重命名 package 之后还需要改代码，默认为 "") 编译器会在链接期自动加上所属的包名称。中点 · 比较特殊，是一个 unicode 的中点，该点在 mac 下的输入方法是 option+shift+9。在程序被链接之后，所有的中点 ·都会被替换为句号 .，比如你的方法是 runtime·main，在编译之后的程序里的符号则是 runtime.main。简单总结一下, Go 汇编实现函数声明，格式为: 静态基地址(static-base) 指针 | | add函数入参+返回值总大小 | |TEXT pkgname·add(SB),NOSPLIT,$16-24 | | |函数所属包名 函数名 add函数栈帧大小函数栈帧大小：局部变量+可能需要的额外调用函数的参数空间的总大小，不包括调用其它函数时的 ret address 的大小。(SB): SB 是一个虚拟寄存器，保存了静态基地址(static-base) 指针，即我们程序地址空间的开始地址。"".add(SB) 表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲，它有一个直接的绝对地址: 是一个全局的函数符号。NOSPLIT: 向编译器表明，不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下，编译器自己帮我们插入了这个标记: 它足够聪明地意识到，add 不会超出当前的栈，因此没必要调用函数时在这里执行栈检查。4.4.2 变量声明汇编里的全局变量，一般是存储在 .rodata或者 .data段中。对应到 Go 代码，就是已初始化过的全局的 const、var 变量/常量。使用 DATA 结合 GLOBL 来定义一个变量。DATA 的用法为:DATA symbol+offset(SB)/width, value大多数参数都是字面意思，不过这个 offset 需要注意：其含义是该值相对于符号 symbol 的偏移，而不是相对于全局某个地址的偏移。GLOBL 汇编指令用于定义名为 symbol 的全局变量，变量对应的内存宽度为 width，内存宽度部分必须用常量初始化。GLOBL ·symbol(SB), width下面是定义了多个变量的例子:DATA ·age+0(SB)/4, $8 ;; 数值8为 4字节GLOBL ·age(SB), RODATA, $4 DATA ·pi+0(SB)/8, $3.1415926 ;; 数值3.1415926为float64, 8字节GLOBL ·pi(SB), RODATA, $8 DATA ·year+0(SB)/4, $2020 ;; 数值2020为 4字节GLOBL ·year(SB), RODATA, $4 ;; 变量hello 使用2个DATA来定义DATA ·hello+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello+8(SB)/8, $" world" ;; ` world` 共8个字节(3个空格)GLOBL ·hello(SB), RODATA, $16 ;; `hello my world` 共16个字节 DATA ·hello<>+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello<>+8(SB)/8, $" world" ;; ` world` 共8个字节(3个空格)GLOBL ·hello<>(SB), RODATA, $16 ;; `hello my world` 共16个字节大部分都比较好理解，不过这里引入了新的标记 <>，这个跟在符号名之后，表示该全局变量只在当前文件中生效，类似于 C 语言中的 static。如果在另外文件中引用该变量的话，会报 relocation target not found 的错误。5. 手写汇编实现功能在 Go 源码中会看到一些汇编写的代码，这些代码跟其他 go 代码一起组成了整个 go 的底层功能实现。下面，我们通过一个简单的 Go 汇编代码示例来实现两数相加功能。5.1 使用 Go 汇编实现 add 函数Go 代码package main func add(a, b int64) int64 func main(){ println(add(2,3))}Go 源码中 add()函数只有函数签名，没有具体的实现（使用 GO 汇编实现）使用 Go 汇编实现的 add()函数TEXT ·add(SB), $0-24 ;; add栈空间为0，入参+返回值大小=24字节 MOVQ x+0(FP), AX ;; 从main中取参数：2 ADDQ y+8(FP), AX ;; 从main中取参数：3 MOVQ AX, ret+16(FP) ;; 保存结果到返回值 RET把 Go 源码与 Go 汇编编译到一起(我这里，这两个文件在同一个目录)go build -gcflags "-N -l" .我这里目录为 demo1，所以得到可执行程序 demo1，运行得到结果：55.2 反编译可执行程序对 5.1 中得到的可执行程序 demo1 使用 objdump 进行反编译，获取汇编代码go tool objdump -s "main\." demo1得到汇编......TEXT main.main(SB) /root/go/src/demo1/main.go main.go:5 0x4581d0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX main.go:5 0x4581d9 483b6110 CMPQ 0x10(CX), SP main.go:5 0x4581dd 7655 JBE 0x458234 main.go:5 0x4581df 4883ec28 SUBQ $0x28, SP ;;生成main栈桢 main.go:5 0x4581e3 48896c2420 MOVQ BP, 0x20(SP) main.go:5 0x4581e8 488d6c2420 LEAQ 0x20(SP), BP main.go:6 0x4581ed 48c7042402000000 MOVQ $0x2, 0(SP) ;;参数值 2 main.go:6 0x4581f5 48c744240803000000 MOVQ $0x3, 0x8(SP) ;;参数值 3 main.go:6 0x4581fe e83d000000 CALL main.add(SB);;call add main.go:6 0x458203 488b442410 MOVQ 0x10(SP), AX main.go:6 0x458208 4889442418 MOVQ AX, 0x18(SP) main.go:6 0x45820d e8fe2dfdff CALL runtime.printlock(SB) main.go:6 0x458212 488b442418 MOVQ 0x18(SP), AX main.go:6 0x458217 48890424 MOVQ AX, 0(SP) main.go:6 0x45821b e87035fdff CALL runtime.printint(SB) main.go:6 0x458220 e87b30fdff CALL runtime.printnl(SB) main.go:6 0x458225 e8662efdff CALL runtime.printunlock(SB) main.go:7 0x45822a 488b6c2420 MOVQ 0x20(SP), BP main.go:7 0x45822f 4883c428 ADDQ $0x28, SP main.go:7 0x458233 c3 RET main.go:5 0x458234 e89797ffff CALL runtime.morestack_noctxt(SB) main.go:5 0x458239 eb95 JMP main.main(SB) ;; 反编译得到的汇编与add_amd64.s文件中的汇编大致操作一致TEXT main.add(SB) /root/go/src/demo1/add_amd64.s add_amd64.s:2 0x458240 488b442408 MOVQ 0x8(SP), AX ;; 获取第一个参数 add_amd64.s:3 0x458245 4803442410 ADDQ 0x10(SP), AX ;;参数a+参数b add_amd64.s:5 0x45824a 4889442418 MOVQ AX, 0x18(SP) ;;保存计算结果 add_amd64.s:7 0x45824f c3 RET通过上面操作，可知：(FP)伪寄存器，只有在编写 Go 汇编代码时使用。FP 伪寄存器指向 caller 传递给 callee 的第一个参数使用 go tool compile / go tool objdump 得到的汇编中看不到(FP)寄存器的踪影6. Go 调试工具这里推荐 2 个 Go 代码调试工具。6.1 gdb 调试 Go 代码测试代码package main type Ier interface{ add(a, b int) int sub(a, b int) int} type data struct{ a, b int} func (*data) add(a, b int) int{ return a+b} func (*data) sub(a, b int) int{ return a-b} func main(){ var t Ier = &data{3,4} println(t.add(1,2)) println(t.sub(3,2))}编译 go build -gcflags "-N -l" -o main使用 GDB 调试> gdb main GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7Copyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.htmlThis is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-redhat-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /root/go/src/interface/main...done.Loading Go Runtime support.(gdb) list // 显示源码14 func (*data) add(a, b int) int{15 return a+b16 }1718 func (*data) sub(a, b int) int{19 return a-b20 }212223 func main(){(gdb) list24 var t Ier = &data{3,4}2526 println(t.add(1,2))27 println(t.sub(3,2))28 }29(gdb) b 26 // 在源码26行处设置断点Breakpoint 1 at 0x45827c: file /root/go/src/interface/main.go, line 26.(gdb) rStarting program: /root/go/src/interface/main Breakpoint 1, main.main () at /root/go/src/interface/main.go:2626 println(t.add(1,2))(gdb) info locals // 显示变量t = {tab = 0x487020 <data,main.Ier>, data = 0xc000096000}(gdb) ptype t // 打印t的结构type = struct runtime.iface { runtime.itab *tab; void *data;}(gdb) p *t.tab.inter // 打印t.tab.inter指针指向的数据$2 = {typ = {size = 16, ptrdata = 16, hash = 2491815843, tflag = 7 '\a', align = 8 '\b', fieldAlign = 8 '\b', kind = 20 '\024', equal = {void (void *, void *, bool *)} 0x466ec0, gcdata = 0x484351 "\002\003\004\005\006\a\b\t\n\f\r\016\017\020\022\025\026\030\033\034\036\037\"&(,-5<BUXx\216\231\330\335\377", str = 6568, ptrToThis = 23808}, pkgpath = {bytes = 0x4592b4 ""}, mhdr = []runtime.imethod = {{name = 277, ityp = 48608}, {name = 649, ityp = 48608}}}(gdb) disass // 显示汇编Dump of assembler code for function main.main: 0x0000000000458210 <+0>: mov %fs:0xfffffffffffffff8,%rcx 0x0000000000458219 <+9>: cmp 0x10(%rcx),%rsp 0x000000000045821d <+13>: jbe 0x458324 <main.main+276> 0x0000000000458223 <+19>: sub $0x50,%rsp 0x0000000000458227 <+23>: mov %rbp,0x48(%rsp) 0x000000000045822c <+28>: lea 0x48(%rsp),%rbp 0x0000000000458231 <+33>: lea 0x10dc8(%rip),%rax # 0x469000 0x0000000000458238 <+40>: mov %rax,(%rsp) 0x000000000045823c <+44>: callq 0x40a5c0 <runtime.newobject>常用的 gdb 调试命令runcontinuebreakbacktrace 与 frameinfo break、localslist 命令print 和 ptype 命令disass除了 gdb，另外推荐一款 gdb 的增强版调试工具 cgdbhttps://cgdb.github.io/效果如下图所示，分两个窗口：上面显示源代码，下面是具体的命令行调试界面(跟 gdb 一样)：#### 6.2 delve 调试代码delve 项目地址https://github.com/go-delve/delve带图形化界面的 dlv 项目地址https://github.com/aarzilli/gdlvdlv 的安装使用，这里不再做过多讲解，感兴趣的可以尝试一下。gdb 作为调试工具自是不用多说，比较老牌、强大，可以支持多种语言。delve 则是使用 go 语言开发的，用来调试 go 的工具，功能也是十分强大，打印结果可以显示 gdb 支持不了的东西，这里不再做过多讲解，有兴趣的可以查阅相关资料。7. 总结对于 Go 汇编基础大致需要熟悉下面几个方面：通过上面的例子相信已经让你对 Go 的汇编有了一定的理解。当然，对于大部分业务开发人员来说，只要看的懂即可。如果想进一步的了解，可以阅读相关的资料或者书籍。最后想说的是：鉴于个人能力有限，在阅读过程中你可能会发现存在的一些问题或者缺陷，欢迎各位大佬指正。如果感兴趣的话，也可以一起私下交流。8. 参考资料在整理的过程中，部分参考、引用下面链接地址内容。有一些写的还是不错的，感兴趣的同学可以阅读。[1] https://github.com/cch123/golang-notes/blob/master/assembly.md plan9 assembly[2] https://segmentfault.com/a/1190000019753885 汇编入门[3] https://www.davidwong.fr/goasm/ Go Assembly by Example[4] https://juejin.im/post/6844904005630443533#heading-3[5] https://github.com/go-internals-cn/go-internals/blob/master/chapter1_assembly_primer/README.md[6] https://lrita.github.io/2017/12/12/golang-asm/[7] https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-01-basic.html
AI摘要 本文探讨了学习新编程语言的方法和常见问题。作者指出，仅阅读官方文档和查看开源项目代码对初学者来说并不足够，因为这些资料可能过于复杂或不全面。作者建议初学者从官方提供的Step-By-Step代码开始学习，这些代码由浅入深、有质量保证且更新及时。同时，作者强调了实践的重要性，建议初学者通过小项目逐步提高。最后，作者呼吁开发者社区中的老手和新手之间需要更多的耐心和理解，以减少信息不对称带来的矛盾。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 首先要说，这并不是一篇教你如何学习的文章，因为到今天为止我也没有找到一种通用的方法来解决如何学习的问题。但是在探索的道路上，我确实产生过一些思路，我想把这些思考的过程分享出来让大家探讨。如果这对你有帮助的话，那我会非常高兴。我最近在学习 Rust ，这是一门很酷但是相对冷门的语言（学习冷门语言可能是我的一大爱好，比如我就非常喜欢 CoffeeScript ，这是 JavaScript 的一门方言）。自从在某些语言的舒适区待久之后，经常会产生自己无所不能的错觉，这也是我不断想学习新语言的一大动力。而我的学习过程跟大多数人类似，就是先在网上找文档然后自学。而在看文档的过程中，我就发现了一些问题。手册的作用“去看文档喽。”这是老鸟在面对新手时最喜欢扔的一句话，我通常也是这么扔给自己的。但当你打开一门语言的手册，你会发现它除了教你语法外，几乎没有教你什么其它东西。于是你会陷入这样一个境地：“背下来了么？”“背不下来，太难懂了。”“再看一遍，给我背下来。”。。。“背下来了么？”“大部分背下来了。”“给我写个程序，实现个XX功能。”“不会。。。”“。。。”在一个高级开发者眼里，你会了语法理所当然就应该会写程序了，但实际情况并非如此。这让我想起前段时间跟一个朋友关于目前编程教育市场的一个讨论。我出于自己的经验提出一个想法：让大牛开发者来教新手入门。在我的设想中，大牛开发者拥有强大的实战经验，以及丰富的专业知识，更是自带光环，这不比现在市场上那些半吊子的讲师好多了。在我为自己“伟大”的想法得意的时候，朋友给我浇了一盆冷水。他告诉我这样根本行不通，大牛根本不知道菜鸟需要什么知识，你可能以为基础语法讲清楚就好了，人家却连编辑器是什么都不清楚。设想一下，让一个大学教授去教一群小学生，这对两者来说都是一种灾难吧。这些语言的创造者，或者文档的作者，无疑都是一些大神。它们在撰写一个语言手册的过程中，只能尽量负责地把这个语言的全貌准确地有组织地展现给你。然而这种全面的展现，对于一个没有任何引导的初学者来说并不完全是一件好事。简单来说就是，你会在一些次要的事情上浪费太多时间，而一些主要的概念又没有理解透彻。关于看真实代码当觉得文档满足不了你的时候，老鸟们往往会扔给你第二招：看代码。看什么代码呢？那还用说，当然是被业界奉为经典的，在 GitHub 上至少有一万颗星的知名开源项目代码啦。当你怀着崇敬的心情，打开扔给你的网址，看着满屏幕的代码时，你会对自己问出那三个经典的哲学问题。这是什么东西？它是怎么做到的？为什么要这么写？搞不好某些人还会因为惊吓过度，从而失去了学习下去的信心。那么让我们一起来看看这些代码里有什么鬼东西，会吓得新手信心不足。大段的注释说一件事你们不要笑，在我还是个萌新的时候，我曾经对这些穿插在代码中的神秘文字产生了深深的困惑，我还以为它们对代码有某种加成作用，以至于我还试验过把注释去掉会对代码执行产生什么影响。而现实中好的代码会让后面的维护者方便很多，但不好的甚至错误的注释会让人迷惑不已。语法糖语法糖是个好东西，它大大简化了我们的编程过程，高手用起语法糖写起代码来简直不要太爽，所以越强大的项目这玩意儿越多。但是对于初学者来说，语法糖隐藏了一些细节，而且让语法看起来很怪异。有些代码如果你用标准语法来写是很好懂的，但如果用语法糖来写的话很难让人一下子明白。初学者为了弄懂这些语法往往要花大量时间，但其实这些时间在这个阶段是没必要的浪费。你看不懂它觉得它是一个很重要的东西，其实它只是一个做工精巧的小玩意儿，离开了它这些代码照样能工作。而随着你的经验丰富，也可以随时随地用起来，用的方法可能也不尽相同。代码里的重点对于一个开源项目来说，往往 50% 的代码可能都是在适配各种不同的运行环境，将系统的 API 抽象成项目里通用的接口，这部分代码除非你自己要做类似的项目，要不然的话对初学者来说参考意义不大。更何况，为了适配某些系统奇葩的运行环境，开发者往往会大开脑洞，创造出一些非常奇怪的代码。这些旁门左道充满了玄学和不确定性，初学者看多了可能会发生如武侠小说里练功出差错的结果：走火入魔。剩下的代码里 20% 是项目内部的接口抽象和定义，最后 30% 才是真正值得看的东西，它们往往散落在各个地方，但又为了一个设计核心服务。让初学者识别出这些代码来，未免太强人所难。野文档这是我自己的一个定义，我把一切非官方的开发文档都称为野文档。初学者会在搜索引擎里得到大量的相关文档，他们很多是学习心得，很多人在写这篇文章时水平可能比你也高不了多少。这就造成了这些文档的水平参差不齐，所面向的重点也不同，也许你花了大把时间弄懂的是一个错误的或者过时的知识。而大部分号称教你入门的文章，可能也就是告诉了你如何搭建一个运行环境，这门语言的精髓和重点作者自己估计也没弄明白。而如果你碰到一篇被奉为经典的好的入门文章，那你真的要好好感谢作者。因为这意味着作者付出了大量的深入思考，深入浅出这四个字说起来简单，做起来可是需要相当的功底的。相对较好的做法在这么多语言的学习过程中，我也总结了一些相对比较好的学习方法。我认为看代码是非常有必要的，因为光死记语法是无法掌握好这门语言的。但是去看一些大型项目代码的缺点我在上面也说了，到底该如何是好呢？我建议大家可以去看官方给出的，专门供初学者学习的 Step-By-Step 代码，这种代码一般在官方的文档页就可以找到链接入口，它有如下好处：由浅入深，富有层次。这些代码往往是跟随者文档的深入，慢慢把语法细节展开的。不会一下给你展现太多，让你无法 GET 到重点。每段代码都会有一个重点要表现的特性，这样看起来会一目了然。有质量保证。这些代码的撰写一般是官方人员负责，这可以在很大程度上保证准确性。更新及时。我们知道很多语言的细节是会随着版本的升级有所改变的，而很多网上的第三方文档往往缺乏维护，但官方文档一般都会同步更新。我建议大家一边看手册一边看代码，这样印象会更深刻。眼睛看了之后，我认为要尽快找一些好的例子来练手，不需要一上来就搞比较复杂的大型项目，也是由浅入深。这种练手项目去哪里找呢，很多语言的 tutorial 板块就是干这个的，做的比较完善的甚至还提供在线的教学体验环境，大家应该好好利用。写在最后这篇讨论学习方法的文章引发了我的一些额外思考。我经常在开发者社区里看到老鸟和初学者互怼，写这篇文章的过程也让我理性思考了产生这些矛盾的原因。总的来说就是一些信息的不对称造成的。老鸟认为这个问题根本不是问题，网上一搜一大把，不愿多讲甚至冷嘲热讽。而初学者却觉得这些信息根本理解不能，老鸟的嘲讽就是一种羞辱。我认为要打破这种不对称需要双方付出耐心，而这种耐心的付出是相互的，你付出的多回报的就越多。而最先迈出付出这一步的，应该是初学者，毕竟从情理上来说是你请求人家办事。而你需要付出的不过是把自己的问题讲明白，说出你的思考过程，附上必要的信息。一个好学的人是不会让人讨厌的，但前提是你得有一个不让人讨厌的姿态展现出来。文章来源：https://joyqi.com/develop/how-to-learn-a-programming-language.html
AI摘要 这篇文章介绍了作者在制作网页时常用的Firefox插件，包括Web Developer Toolbar、CssViewer、Firebug、IEtab、X-ray、FasterFox、HTML Validator、ColorZilla、MeasureIT等，这些插件分别用于网页设计、调试、浏览器兼容性测试、性能测试、校验XHTML标准、获取网页配色、测量页面区域大小等。此外，作者还推荐了一些日常使用的Firefox插件，如Fireftp、AdBlock Plus、All-in-one Gestures、FlashGot、Foxyproxy、MediaWrap、Performancing、Search Status、Tab Mix Plus、Videodownloader等，这些插件功能涵盖FTP、广告过滤、鼠标手势、下载、代理切换、在线音频播放、博客写作、SEO工具、标签浏览、视频下载等。作者还表达了对一个类似Putty的SSH功能的Firefox插件的期待。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 我制作网页时会用的几个FIREFOX插件1. Web Developer Toolbar功能非常强大，是做网页设计师的必备工具。2. CssViewer可以在网页中查看某个区域的CSS， 我常常用它研究一些特别的效果是如何做出来的。3. Firebug用来调试网页，如果HTML，CSS或者JS出错，它会有对应提示。4. IEtab在FF切换到IE，省去再打开一个IE浏览器的麻烦。对于测试页面在各种浏览器的兼容时特别有用。5. X-ray名副其实的插件-> X射线，使用了之后直接在页面中查看网页对应结构，比较直观，而不需要查看源代码，这个插件和CSSViewer配合特别有用。6. FasterFox提升FF性能的工具，我用来测试页面的载入时间。7. HTML Validator校验XHTML是否标准，虽然比不上W3C的validator, 但是对于一般应用而言已经足够。8. ColorZilla截取网页的配色工具。9. MeasureIT尺子，量取页面的某个区域的大小。另外有一些我日常使用FF插件，虽然不用于网页制作，但是也很有用。1. FireftpFF的FTP插件。2. AdBlock Plus过滤网页广告的插件。3. All-in-one Gestures鼠标手势4. FlashGot调用下载软件替代FF的下载功能5. FoxyproxyFF的代理切换软件，可以设置白名单，黑名单，自带TOR配置向导，配合TOR可以冲破GFW的封锁。6. MediaWrapFF的在线音频播放插件。7. PerformancingBloging 工具，这篇日志就是通过它写成。支持许多BLOG软件，比如Wordpress, MT.8. Search Status查看网页的PR，Alexa工具。SEOer 必备。9. Tab Mix Plus标签浏览10. Videodownloader 可以下载Youtube等网站的 .flv 视频。我想要的FF插件:希望有一个类似Putty.exe的FF插件，这样我就可以直接在FF的窗口中 SSH 。 了不过我想很多人不需要用到这个功能的，因为不管是Linux,还是Mac OS X, 都可以直接SSH， 而且即使在Windows下，也有Putty.exe可以用。但是我觉得，如果Firefox有这样一个插件的话，那么，所谓的 WebOS 就不远了。文章来源：http://blog.vingel.com/post/23.html