精华内容
下载资源
问答
  • 外部链接和内部链接的重要性
    千次阅读
    2020-03-23 12:26:48

    该存储类别的变量具有静态存储期、文件作用域和内部链接。

    在所有函数外部(这点与外部变量相同),用存储类别说明符static定义的变量具有 这种存储类别:

    static int svil = 1;  // 静态变量,内部链接
    int main(void)
    {
    

    这种变量过去称为外部静态变量(external static variable),但是这个术语有点自相矛盾(这些变量具有内部链接)。但是,没有合适的新简称, 所以只能用内部链接的静态变量(static variable with internal linkage)。普通 的外部变量可用于同一程序中任意文件中的函数,但是内部链接的静态变量 只能用于同一个文件中的函数。可以使用存储类别说明符 extern,在函数中 重复声明任何具有文件作用域的变量。这样的声明并不会改变其链接属性。 考虑下面的代码:

    int traveler = 1;      // 外部链接
    static int stayhome = 1;  // 内部链接
    int main()
    {
    extern int traveler;  // 使用定义在别处的 traveler
    extern int stayhome;  // 使用定义在别处的 stayhome
    ...
    

    对于该程序所在的翻译单元,trveler和stayhome都具有文件作用域,但是只有traveler可用于其他翻译单元(因为它具有外部链接)。这两个声明都使用了extern关键字,指明了main()中使用的这两个变量的定义都在别处, 但是这并未改变stayhome的内部链接属性。

    多文件

    只有当程序由多个翻译单元组成时,才体现区别内部链接和外部链接的 重要性。接下来简要介绍一下。
    复杂的C程序通常由多个单独的源代码文件组成。有时,这些文件可能
    要共享一个外部变量。
    C通过在一个文件中进行定义式声明,然后在其他文件中进行引用式声明来实现共享
    也就是说,除了一个定义式声明外,其他 声明都要使用extern关键字。而且,只有定义式声明才能初始化变量。
    注意,如果外部变量定义在一个文件中,那么其他文件在使用该变量之 前必须先声明它(用 extern关键字)。也就是说,在某文件中对外部变量进 行定义式声明只是单方面允许其他文件使用该变量,其他文件在用extern声 明之前不能直接使用它。
    过去,不同的编译器遵循不同的规则。例如,许多 UNIX系统允许在多 个文件中不使用 extern 关键字声明变量,前提是只有一个带初始化的声明。 编译器会把文件中一个带初始化的声明视为该变量的定义。

    存储类别说明符

    你可能已经注意到了,关键字static和extern的含义取决于上下文。
    C 语言有6个关键字作为存储类别说明符:auto、register、static、extern、 _Thread_local和typedef。typedef关键字与任何内存存储无关,把它归于此类 有一些语法上的原因。
    尤其是,在绝大多数情况下,不能在声明中使用多个 存储类别说明符,所以这意味着不能使用多个存储类别说明符作为typedef的 一部分。唯一例外的是_Thread_local,它可以和static或extern一起使用。

    auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。 由于在块中声明的变量本身就具有自动存储期,所以使用auto主要是为了明 确表达要使用与外部变量同名的局部变量的意图。

    register 说明符也只用于块作用域的变量,它把变量归为寄存器存储类 别,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取。
    用 static 说明符创建的对象具有静态存储期,载入程序时创建对象,当 程序结束时对象消失。如果static 用于文件作用域声明,作用域受限于该文 件。如果 static 用于块作用域声明,作用域则受限于该块。因此,只要程序 在运行对象就存在并保留其值,但是只有在执行块内的代码时,才能通过标识符访问。块作用域的静态变量无链接。文件作用域的静态变量具有内部链 接。

    **extern 说明符表明声明的变量定义在别处。**如果包含 extern 的声明具有 文件作用域,则引用的变量必须具有外部链接。如果包含 extern 的声明具有 块作用域,则引用的变量可能具有外部链接或内部链接,这接取决于该变量 的定义式声明。

    小结:存储类别

    1. 自动变量具有块作用域、无链接、自动存储期。它们是局部变量,属于 其定义所在块(通常指函数)私有。
    2. 寄存器变量的属性和自动变量相同,但是编译器会使用更快的内存或寄存器储存它们。不能获取寄存器变量的地址。
    3. 具有静态存储期的变量可以具有外部链接、内部链接或无链接。在同一 个文件所有函数的外部声明的变量是外部变量,具有文件作用域、外部链接 和静态存储期。如果在这种声明前面加上关键字static,那么其声明的变量 具有文件作用域、内部链接和静态存储期。如果在函数中用 static 声明一个 变量,则该变量具有块作用域、无链接、静态存储期。
    4. 具有自动存储期的变量,程序在进入该变量的声明所在块时才为其分配 内存,在退出该块时释放之前分配的内存。如果未初始化,自动变量中是垃圾值。
    5. 程序在编译时为具有静态存储期的变量分配内存,并在程序的运行过程中一直保留这块内存。如果未初始化,这样的变量会被设置为0。
    6. 具有块作用域的变量是局部的,属于包含该声明的块私有。
    7. 具有文件作用域的变量对文件(或翻译单元)中位于其声明后面的所有函数可见。
    8. 具有外部链接的文件作用域变量,可用于该程序的其他翻译单元。
    9. 具有内部链接 的文件作用域变量,只能用于其声明所在的文件内。
      下面用一个简短的程序使用了5种存储类别。该程序包含两个文件,所以必须使用多文件编译。该示例仅为了让读者熟悉5种存储类别的用法,并不 是提供设计模型,好的设计可以不需要使用文件作用域变量。
    // parta.c --- 不同的存储类别
    // 与 partb.c 一起编译
    #include <stdio.h>
    void report_count();
    void accumulate(int k);
    int count = 0;     // 文件作用域,外部链接
    int main(void)
    {
    int value;     // 自动变量
    register int i;  // 寄存器变量
    printf("Enter a positive integer (0 to quit): ");
    while (scanf("%d", &value) == 1 && value > 0)
    {
    ++count;    // 使用文件作用域变量
    for (i = value; i >= 0; i--)
    accumulate(i);
    printf("Enter a positive integer (0 to quit): ");
    }
    report_count();
    return 0;
    }
    void report_count()
    {
    printf("Loop executed %d times\n", count);
    }
    程序清单12.6 partb.c程序
    // partb.c -- 程序的其余部分
    // 与 parta.c 一起编译
    #include <stdio.h>
    extern int count;      // 引用式声明,外部链接
    static int total = 0;    // 静态定义,内部链接
    void accumulate(int k);   // 函数原型
    void accumulate(int k)// k 具有块作用域,无链接
    {
    static int subtotal = 0;  // 静态,无链接
    if (k <= 0)
    {
    printf("loop cycle: %d\n", count);
    printf("subtotal: %d; total: %d\n", subtotal, total);
    subtotal = 0;
    }
    else
    {
    subtotal += k;
    total += k;
    }
    }
    

    在该程序中,块作用域的静态变量subtotal统计每次while循环传入 accumulate()函数的总数,具有文件作用域、内部链接的变量 total 统计所有 传入 accumulate()函数的总数。当传入负值时, accumulate()函数报告total和 subtotal的值,并在报告后重置subtotal为0。由于parta.c调用了 accumulate()函 数,所以必须包含 accumulate()函数的原型。而 partb.c 只包含了accumulate() 函数的定义,并未在文件中调用该函数,所以其原型为可选(即省略原型也不影响使用)。该函数使用了外部变量count 统计main()中的while循环迭代 的次数(顺带一提,对于该程序,没必要使用外部变量把 parta.c 和 partb.c 的代码弄得这么复杂)。在 parta.c 中,main()和report_count()共享count。
    下面是程序的运行示例:

    Enter a positive integer (0 to quit): 5
    loop cycle: 1
    subtotal: 15; total: 15
    Enter a positive integer (0 to quit): 10
    loop cycle: 2
    subtotal: 55; total: 70
    Enter a positive integer (0 to quit): 2
    loop cycle: 3
    subtotal: 3; total: 73
    Enter a positive integer (0 to quit): 0
    Loop executed 3 times
    
    更多相关内容
  • 上篇讲了”网站SEO关键词布局操作大全“,这篇讲链接,网站是通过一个一个链接搭建到一块的,所有网站之间以及网站用户之间也都是通过链接进行联系起来的,同样也是搜索引擎爬取识别的线路,那么都有哪类链接?...

    上篇讲了”网站SEO关键词布局操作大全“,这篇讲链接,网站是通过一个一个链接搭建到一块的,所有网站之间以及网站和用户之间也都是通过链接进行联系起来的,同样也是搜索引擎爬取和识别的线路,那么都有哪类链接?他们的作用是什么?应该注意什么问题?下面就把所有有关链接方面的知识整理收集到一块,方便大家学习研究。

    各类链接的使用方法合集

    一、常见链接类型

    为了方便,把链接也分出了很多种,每种链接都有其自己相应的作用,同时链接直接也有共同和相通的地方,如果我们知道了这些链接的种类和作用,使用起来是不是更方便。

    二、友情链接交换

    只所以单独讲友情链接,因为它一直在优化中占重要作用,随着搜索引擎对各种链接的筛选和打击(绿萝算法绿萝算法2.0),目前真正起作用的外链越来越少,但友情链接的作用和地位一直没受到过影响,需要好好了解。

    三、外链发布技巧

    虽然外链在网站优化中的比重越来越小,但对于SEO的作用还非常的大,这也是搜索引擎做排名是必须考虑的一方面,现在很多外链被视为垃圾外链,不过还是有很多发布高质量外链的方法技巧值得我们学习。

    四、外链建设思考

    主要是谈谈外链的必要性以及如果做才能投入最少的精力获取最大的效果,可以说是外链建设技巧的进一步思考,不管做什么事情都要有计划和步骤循序渐进,外链很简单却也有很多细节方面需要注意。

    以上内容是对网站优化链接建设的详细讲解,已经包含了各种链接问题可以说非常全面了,如果能详细的学习和研究那么关于链接建设一定会没有任何问题的,不管是什么学到的东西都要用到实践中,否则一切白搭。

    原文地址:http://shenyongxiang.com/syx/1234.html

    展开全文
  • 一: Scheme协议完成外部链接跳转app指定页面 什么是 URL Scheme?  android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;通过...

    一: Scheme协议完成外部链接跳转app指定页面

    什么是 URL Scheme?

        android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;通过scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等。

    URL Scheme应用场景:

        客户端应用可以向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用中启动本应用。通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如商品详情页、活动详情页等等。也可以执行某些指定动作,如完成支付等。也可以在应用内通过 html 页来直接调用显示 app 内的某个页面。综上URL Scheme使用场景大致分以下几种:

    • 服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面
    • H5页面点击锚点,根据锚点具体跳转路径APP端跳转具体的页面
    • APP端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面
    • APP根据URL跳转到另外一个APP指定页面

    URL Scheme协议格式:

       先来个完整的URL Scheme协议格式:

    xl://goods:8888/goodsDetail?goodsId=10011002

    通过上面的路径 Scheme、Host、port、path、query全部包含,基本上平时使用路径就是这样子的。

    • xl代表该Scheme 协议名称
    • goods代表Scheme作用于哪个地址域
    • goodsDetail代表Scheme指定的页面
    • goodsId代表传递的参数
    • 8888代表该路径的端口号

    URL Scheme如何使用:

     1.)在AndroidManifest.xml中对<activity />标签增加<intent-filter />设置Scheme

     <activity
                android:name=".GoodsDetailActivity"
                android:theme="@style/AppTheme">
                <!--要想在别的App上能成功调起App,必须添加intent过滤器-->
                <intent-filter>
                    <!--协议部分,随便设置-->
                    <data android:scheme="xl" android:host="goods" android:path="/goodsDetail" android:port="8888"/>
                    <!--下面这几行也必须得设置-->
                    <category android:name="android.intent.category.DEFAULT"/>
                    <action android:name="android.intent.action.VIEW"/>
                    <category android:name="android.intent.category.BROWSABLE"/>
                </intent-filter>
            </activity>

    2.)获取Scheme跳转的参数

    Intent i_getvalue = getIntent();
    String action = i_getvalue.getAction();
    if(Intent.ACTION_VIEW.equals(action)){

    Uri uri = i_getvalue.getData();
    if (uri != null) {
    
    // 完整的url信息 String url = uri.toString(); Log.e(TAG, "url: " + uri); // scheme部分 String scheme = uri.getScheme(); Log.e(TAG, "scheme: " + scheme); // host部分 String host = uri.getHost(); Log.e(TAG, "host: " + host); //port部分 int port = uri.getPort(); Log.e(TAG, "host: " + port); // 访问路劲 String path = uri.getPath(); Log.e(TAG, "path: " + path); List<String> pathSegments = uri.getPathSegments(); // Query部分 String query = uri.getQuery(); Log.e(TAG, "query: " + query); //获取指定参数值 String goodsId = uri.getQueryParameter("goodsId"); Log.e(TAG, "goodsId: " + goodsId);}
    }

    3.)调用方式

    网页上

    <a href="xl://goods:8888/goodsDetail?goodsId=10011002">打开商品详情</a>

     4.)如何判断一个Scheme是否有效

    PackageManager packageManager = getPackageManager();
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("xl://goods:8888/goodsDetail?goodsId=10011002"));
    List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
    boolean isValid = !activities.isEmpty();
    if (isValid) {
        startActivity(intent);
    }




    二:JS和安卓互相调用
    下面上代码:(js_webView.html)

    html里面的代码也比较简单,整个html中就一个Button,点击这个Button的时候去执行javascript中的 jsCallAndroid() 方法。

    网页中有关 javascript 的代码也比较简单,整个 js 就2个方法,一个是 jsCallAndroid() ,一个是 androidCallJs() 。看方法名就知道了,分别是 js 调用 Android 的和 Android 调用 js 的。

    先不要去管 jsCallAndroid() 里面做的是什么,待会会解释,来看看 androidCallJs() 这个方法里面做的就是弹出一个信息提示框,具体提示什么信息都不重要了,随便。

    再来看看代码部分(WebViewActivity的布局文件):


    整个布局文件也很简单,一个按钮和一个WebView,按钮是用来测试 Android 调用 js 用的,js 调用 Android 就当然是 webview 加载的网页里面的按钮了。

    再来看看 Java 代码部分(WebViewActivity):


    下面来看看代码部分:

    37-42行:这几行就是android中的按钮的点击事件,没什么好解释的,来看看点击事件做的是什么?点击事件做的是:调用 webview 的 loadurl 方法去调用 js 中的方法;调用的方式是:前面是 javascript 中间用 : 分隔 最后是 要调用的 js 的方法名。

    45-55行:这几行就是有关于 Webview 的设置等,46-51 这几行是指支持弹窗,也就是支持 html 网页弹框,因为前面的 html 代码中,有我们 Android 调用 js 的时候,调用成功就 js 弹窗,所以这里要加上这个设置。接下来是53行,53行指的是支持 javascript 这里指的是支持 html 中的 javascript 解析,不管是不是 js 和 Android 交互,只要网页中含有 js ,都要。最关键的就是54行,54行就是 javascript 和 Android 交互的了,addJavascriptInterface 方法需要接受两个参数,第一个是与之相对应的 js 调用 Android 本地的类的对象,这个例子中的就是58-63行这个类的对象,第二个参数就是和前面网页中的js代码中的 jsCallAndroid 方法中的 wv.sayHello(),这里的 wv 就和这个参数(wv)与之相对应,而 sayHello() 就是对应的第一个参数的对象里面的方法。

    最后是58-63行,这几行没什么好解释的了,只是如果调用成功就打印一行日志。仅此检验是否调用成功而已。

    整个 demo 代码到此完毕,好激动,赶紧运行试试看。

     

    运行的结果会让很多人失望,只是android调用js成功了,但 js 调用 android 不成功。

     

    这是为什么呢?这里要涉及到的是有关于 webview 和 js 的安全性的问题。js 可以通过这种方式下载恶意代码在 android 上执行,具体有兴趣的可以去 Google 一下,所以上面这种写法只是对于 Api16以前的android手机是适用的,16以后,谷歌对这个安全性问题进行了修复。将其注解到android自带的一个javascriptInterfface类中。下面就来看看16以后的写法是咋样的?


    有了注解,简直如虎添翼,非常方便。还是原来的配方,还是原来的味道,原汁原味。除了 Activity 中的代码需要修改,其他都不动。

    改动的代码有55行,直接传一个 this(Context) 对象就可以了,那么,原来的 JsInterface 就可以不要了。不要那我 sayHello 方法写到哪里呢?既然你传递的是 this ,当然是写到 this 里面咯(59-62行)。不同的是,这个 sayHello() 方法必须加上一个 JavascriptInterface 的注解。

     

    OK了,16以前和16以后的都有了,不就OK了么。在添加javascript的时候判断一下 Api 版本就可以了,哈哈。。。

     

    不不不,肯定不是这样子做。指需要在onCreate() 方法上添加 @SuppressLint("JavascriptInterface") 注解即可。

    看下面的就是终极代码了。


    对,没错,就是这样子。大功告成。

    最后需要提一点的是,上面的例子是可以执行,正常情况下都没什么问题,但你看看网上的demo,很多在 android 调用 js 的时候是开一个子线程去调用,没错,实际开发中,是必须要这样子做的。好处就不言而喻了。这一点看最后一张代码图,这里也有给出,直接调用webview的post,里面就是 Android 调用 js 了。



    展开全文
  • 静态链接可以使得开发者可以专注地开发自己的程序模块,但随着程序规模的增大,静态链接的一次性链接装配存在浪费内存磁盘空间、模块更新困难等问题。甚至说基本的字符串公用库函数,每个程序内部处理都要

    1. 动态链接技术的诉求来源

    静态链接:从目标文件到可执行文件,将所需的所有的模板链接,最终生成单一的可执行文件模块;
    动态链接:单一的可执行文件模块被拆分成若干个模块,在程序运行过程中动态进行链接的方案。

    静态链接可以使得开发者可以专注地开发自己的程序模块,但随着程序规模的增大,静态链接的一次性链接装配存在浪费内存和磁盘空间、模块更新困难等问题。甚至说基本的字符串公用库函数,每个程序内部处理都要保留一份printf(),scanf(),strlen()等函数,还有数量可观的其他库函数及它们所需要的辅助数据结构。一个普通C程序光用到的C语言静态库就至少有1MB以上,那么如果我们的机器运行着100个这样的程序,浪费空间可以达到100MB。Linux操作系统不像Windows那样吃机器,甚至很低配置的机器运行Linux也很流畅,这是得益于Linux很讲究内存空间效率的极致使用,锱铢必较。

    程序开发和发布时,如果采用静态链接,则一旦程序中任何模块有更新,由于静态链接的文件就如同早期的整版木刻印刷版,哪怕改一个字,也得重刻一版,同样静态链接需要将改后后的程序重新链接、发布给用户,即哪怕再小的更新,用户都需要重新下载一次。在早期的网速较慢的年底,这种动辄整体回退的做法,显然不能忍。故而把文件根据功能分成若干模块,链接过程推迟到程序运行时再进行,这便是动态链接的基本思想。显然模块分得越细粒度,模块间的耦合度越小,则更改起来的要变动的部分便越少。

    此外分模块的做法,还让程序间共享模块得以实现,否则每个程序都需要一套C语言stdio.h的独立拷贝。共享模块的好处不仅可以节省内存,还可以减少物理页面的换入换出(减少IO),也可以增加CPU缓存命中率,因为不同进程可能都集中采用了一个共享模块。故而对于动态链接,升级只需要将旧的目标文件覆盖掉即可。动态链接可以使得开发过程中各个模块更加独立,耦合度更小。ELF程序在静态链接下要比动态链接快5%左右,但是采用动态链接节省了磁盘空间和内存空间,并且程序模块升级更便捷。

    简而言之,动态链接便是模块化编程的真正贯彻,虽然管理起来略微繁琐,但是可实现内存空间效率和操作时间效率的双赢。

    2. Linux动态链接的实现

    动态链接涉及运行时链接多个文件,涉及到整个进程的虚拟空间的地址分配和布置,比静态链接的确定性定位排布而言,动态链接中有诸多的地址引用需要到运行时才能确定,此外还涉及到存储管理、内存共享、多线程等机制。故而动态链接需要操作系统支持。目前Linux支持的共享动态文件.so,Windows支持.dll插件型动态链接库。

    静态链接是在将程序装载进虚拟空间之前便完成了所有程序文件的链接,而动态链接是在程序装载进虚拟空间串联式实时装载的,即链接和装载同步进行。比如当前主程序文件maincontent.c引用某个外部库的函数foobar(),则动态链接器会将maincontent.c内对foobar()的引用标记为一个动态链接的符号,不在链接阶段对它进行地址重定位,而把这个过程推迟到主程序文件装载进虚拟进程空间时才进行。至于maincontent.c中如何声明foobar()为外部动态链接符号,则便涉及到了符号导入表等机制。不过既然说到了链接阶段不进行地址重定位,而是将符号引用地址定位推迟到装载时,那么便接着介绍下装载时重定位。

    3. 装载时重定位

    任何文件要被装载进虚拟进程空间,是需要有个装载地址的,显然主程序文件被装载进虚拟进程空间时是没有人和它争的,而其他动态链接库装载时,则可能出现装载地址冲突的问题。

    显然为每个贡献库模块指定固定载入地址,是会严重限制程序升级,甚至导致模块间出现地址冲突。这种做法被称作静态共享库(不是静态库),它的做法是将多种模块统一交给OS,让OS在特定的地址范围内划分出足够的空间预留给这些已知模块。因为静态共享库存在足够先天设计的弊病,导致这种设计很少见,已经被动态链接彻底取代。

    换个说法:.so对象在编译时不能假设自己在进程虚拟空间中的位置,但是可执行文件可以唯一确定自己的起始位置,因为可执行文件往往是第一个被加载的文件,它可以选择一个固定空闲的地址,如Linux下一般是0x08040000,Windows下一般是0x00400000所以对于共享对象文件而言是存在一个所谓“装载时所有地址引用重定位”的需求。那么该如何实现这种重定位目的呢?

    装载时重定位便是针对这个目的的一个直接方法:根据共享对象在装载时的真正位置,遍历文件中所有的绝对地址引用,不过由于整个共享对象是作为一个整体被加载的,故而程序中指令和数据的相对位置是不会变的,如果一个共享对象文件被编译时先预设自己的装载位置为0x1000,但在装载时被分配到0x4000,则程序文件中所有绝对地址引用只需要加上0x3000的整体偏移量即可。

    装载时重定位可以解决动态模块中有绝对地址引用的情况,但是它最大的缺点是在内存中的指令部门将因为绝对地址修改导致在多个进程间无法共享,并不节省内存,只节省了磁盘空间,并不节省内存空间。

    4. 地址无关代码

    我们的目的是尽可能让共享模块中的指令集尽可能被复用,显然这些复用的指令集合是不需受装载地址影响的。所以实现思路是将把指令中需要被修改的部分分离出来,跟需要被修改的RW数据段放在一起,这些RW权限的段是每个进程都需要有一个副本的,而RX权限的纯无需修改.code段则可以被复用,这种技术成为地址无关代码(PIC, Position-independent Code)。也是Linux区别于Windows的一个重要特征。

    显然地址无关技术增加对程序的遍历编译次数,故而在前期编译阶段Linux要花费更多的时间用于区分代码中的装载地址无关性代码和相关性代码。而Windows则采用的是一种所谓的基地址重定位技术,其实和装载时重定位并无本质区别,但是会导致共享对象文件的指令集合一旦出现绝对地址引用并无法共享的结果,这也是为什么说Linux比Windows在某些方面要小家子气。

    5. 共享对象模块的代码段地址无关性处理

    现将共享对象模块的代码段中地址引用的情况分为以下几种情况:
    1. 模块内部的函数调用、跳转
    此情况函数调用者和被调用者的相对位置是固定的,故而模块内部的跳转可以通过相对地址调用,或者是基于寄存器的相对调用,即采用所谓的近址偏移跳转。
    这里写图片描述

    2.模块内部的数据访问,如模块中定义的全局变量、局部静态变量
    任何一条指令与它要访问的模块内部数据之间的相对位置是固定的,那么只需要相对于当前指令加上固定的偏移量就可以访问模块内部数据。现代的体系结构中,数据的相对寻址并没有相对于当前指令地址PC的寻址方式,ELF用了一个巧妙的办法先得到了当前PC值
    这里写图片描述
    对于函数段调用,AT&T汇编集提供了一个call指令使用近址偏移跳转来实现程序流的跳转,而对于数据的相对引用,虽然任何一个文件被编译之后.code和.data段的相对位置已经可以确定了,但是如何利用call指令才实现类似的近址偏移获取.data段中的模块内部数据的地址。上面的ELF汇编指令很巧妙,其制造出一个辅助函数<_i686.get_pc_thunk.cx>,当系统执行call指令以后,下一条指令的地址(这里是454)会被压倒栈顶,而esp寄存器就是始终指向栈顶的,那么当进入<_i686.get_pc_thunk.cx>内部后,ecx寄存器便存放在下一条指令的首地址(即454),这时则返回便可通过加上距.data段的偏移量,再加上a在.data段 中偏移量0x28,便可以获取了模块内数据a的控制权限。

    3.模块外部的数据访问,比如其中模块中定义的全局变量
    模块间的数据访问要比模块内部数据访问复杂点,因为模块外数据的具体位置得等到装载时才能确定,其他模块的全局变量存放在宿主模块的.data段中,这时需要有个中转的数据结构来复杂模块间的数据引用切换,类似于电话接线员一样(假设电话网内的电话号码每天重置,根据用户接入顺序依次分配,假如A用户先接入电话网时,A想和B通信,但是B还未接入电话网,所以A不能通过直接拨号码来直接和B通信,所以A向接线员申请了要和B通信,接线员记录下“A向B发起请求”,这样等到B接入网络时,接线员便将该标签上内容修改成今天B的号码,那么下次A重新发起请求时,接线员便可以将B的号码直接告诉A)。

    类似的原理,Linux将接线员的角色换成了全局偏移表GOT(global offset table),模块将自己对外部模块数据的请求先声明在GOT表中,得到目标模块加载后,再将GOT表相对应的请求项换成对应的数据地址。
    这里写图片描述
    当指令中需要访问变量b时,动态链接器会到GOT表中查找变量b的地址(引用处可能置为b在GOT表中下标),如果当前为空,则等待动态链接器完成GOT表填充。链接器会在装载宿主模块时,遍历每个外部可见性符号的地址,然后填充GOT各项。一般GOT表被放在.data段中,以使在它可以在装载时权限为 RWXP

    4.模块外部的函数调用、跳转等
    函数和变量一样是符号,故而模块间的函数调用也是通过GOT表来实现中转的,只不过此时GOT中相应项保存的是函数的地址。
    这里写图片描述

    5.共享模块的全局变量问题
    面四种情况并没有考虑定义在模块内部的全局变量,粗略地看确实模块内部的全局变量和模块内部的静态变量一样处理不就可以了吗?但是存在特殊情况。

    当一其他模块B引用模块A内部定义的全局变量

    extern int global;
    int foo()
    {
        global = 1;
    }

    当编译器编译到该代码时,它是无法根据上下文判断出该段代码中的global是引用于同一模块的其他文件还是其他模块的全局变量。如果此时该段代码时存在主程序中,因为程序主模块是不需要考虑共享的,故而不会采用地址无关PIC代码分拣工作,故而在主程序中关于该全局变量global的引用和普通数据访问方式一样,编译器会产生类似这样的代码

    movl  $0x1,  xxxxxxx

    其中xxxxxxx便是global的地址,由于可执行文件在运行时是不进行代码重定位的,所以变量的地址必须在链接时确定下来,为了能够使链接过程正常进行,则链接器在加载主程序文件时,会先在它的.bss段中声明一个暂未初始化的global变量副本,但如果global定义在原先的共享对象中,这便会出现一个变量在虚拟进程空间中有两处副本,肯定不行!但是可执行文件的global必须在链接主程序时就要确定,不然没法确定下汇编代码,所以只能让所有的global指向主程序文件创建在.bss段中的那个global副本。

    ELF共享库在编译时,因为不知道自己的全局变量在主程序文件是否已经被“先斩后奏”地先生造出一个.bss副本,故而默认把模块内部的所有全局变量当做引用其他模块的全部变量,通过GOT中转访问。当共享模块被装载时,如果某个全局变量已经在主程序文件中被创建了副本,那么动态链接器就要把GOT中相应符号的地址都指向该.bss副本,如果变量在共享模块已被初始化,还得被初始值复制到主程序的.bss副本中;如果主程序文件并未创建该变量副本,也意味着主程序未声明对该全局变量的使用,那么就是GOT中相应符号的地址指向宿主模块中的全局变量地址。

    通过以上的5种分类,对代码段进行分类,将代码段可以做到地址无关性的保留,有绝对地址引用问题的,通过引入额外的数据结构,实现替代牺牲,将GOT迁移到数据段(如利用GOT表,来作间接访问),而代码本身转化成地址无关性。

    6. 共享对象模块的数据段的地址无关性

    解决了代码段的共享对象动态链接的重定位问题,那么如果数据段也存在绝对地址引用问题?

    static int a;
    static int* p = &a;
    //指针p是一个绝对地址,指向a,但是变量a显然随着模块装载位置不同而出现在不同位置,那么p如何确定?

    因为数据段是每个进程都有一份副本的,所以可以采用装载时重定位的整体绝对地址引用偏移的手法。对于共享对象.so,如果数据段中有绝对地址引用,那么编译器和链接器便会在产生该.so对象时,同时产出一个重定位表.data.rel,该表里包含了“R_386_Relative”类型的重定位入口,当动态链接器装载.so时,发现.so中含有.data.rel,则动态链接器也会对该.so对象进行重定位。

    如果不使用-fPIC参数编译生成.so:
    $gcc -shared pic.c -o pic.so
    那么上面这个命令就会产生一个不使用地址无关代码而使用装载时重定位的共享对象.so,但是如果代码段不是地址无关的,它就不能被多个进程之间共享,于是失去了节省内存的优点,但装载时重定位的共享对象的运行速度要比地址无关代码类型的.so快,因为省去了地址无关代码中每次访问全局数据和函数时需要做一次计算当前PC指令地址以及间接地址寻址的转换过程计算。

    展开全文
  • SEO内部优化与SEO外部优化

    千次阅读 2019-07-10 21:21:42
    SEO优化技术不是几个简单的建议,而是需要足够的耐心精巧的方法。一般来说,SEO优化主要分为八个小步骤:  1,关键字分析  这是SEO优化中最重要的部分。关键词分析包括:关键词关注分析、竞争对手分析、相关SEO...
  • .c或.cpp文件生成可执行文件.exe分为两个过程,即编译过程和链接过程。 编译是把文本形式源代码翻译为机器语言形式的目标文件的过程。 链接是把目标文件、操作系统的启动代码用到的库文件进行组织,形成最终生成...
  • 程序的静态链接,动态链接和装载

    千次阅读 2017-04-14 09:59:07
    转自:程序的静态链接,动态链接和装载 参考自:http://www.xuebuyuan.com/1730287.html 一、程序编译链接的整体流程 二、目标文件的样子(以linux下的elf文件格式为例) 三、静态链接 四、装载 五、...
  • SEO优化的内部链接和外部链接策略

    千次阅读 2009-01-16 10:55:00
    用户通过超级链接获得丰富的网站内容,搜索引擎蜘蛛也是沿着一个网站的页面链接层层跟踪深入,完成对该网站的信息抓取。对搜索引擎尤其是Google来说...这就是外部链接或反向链接,也称导入链接(Inbound links或backli
  • 我们都知道,外部类对非静态内部类是完全透明的,即非静态内部类可以访问外部类包括private成员在内的所有成员,但是,另一个常常被大家忽略的地方是外部类也可以在类的内部通过新建内部类对象来访问非静态内部类...
  • 内部碎片 外部碎片

    千次阅读 2013-10-29 23:07:44
     静态分配在程序编译链接时分配的大小使用寿命就已经确定,而应用上要求操作系统可以提供给进程运行时申请释放任意大小内存的功能,这就是内存的动态分配。  因此动态分配将不可避免会产生内存碎片的问题,...
  • 在做计算机网络的课程设计的时候,遇到了ICMP的一个重要应用,就是利用分组网间探测ping测试两台主机之间的连通 闲来无事,试一试 发现我居然ping不了 遂去百度了一下,说是要添加环境变量,我试了一下,可能是...
  • 重定位和链接

    千次阅读 2016-07-10 11:32:44
    链接和重定位是嵌入式C中很重要的部分,对于这一块掌握的越精细越好。 指令位置分类 指令分为两种: 位置无关编码(PIC):汇编源文件被编码成二进制可执行程序时编码方式与位置(内存地址)无关 位置相关编码:...
  • 为了不产生混淆,本文中的每个子章节标题引用使用的都是官方手册英文原称。命令及命令行选项统一使用斜体书写 ...如果没有为其提供一个,链接器将会使用默认的编译在链接器执行文件内部的脚本。.
  • 其实网站站内链接优化站外链接优化一样重要,两者是相辅相成... 很多站长网站管理员都在拼命的做外部链接,其实外部链接比较受限制,内部链接反而在掌握之中,这个时候建议你先把站内链接优化好再做外部链接,...
  • 前言一、为什么反向链接重要?1.排名2.可发现3.推荐流量二、什么才是好的反向链接?1.关联2.权威3.流量4.放置5.锚文字三、如何检查反向链接1.在Google Search Console中检查反向链接2.使用第三方反向链接检查器...
  • C/C++的编译和链接过程

    千次阅读 2019-06-03 15:49:29
    cl.exelink.exe分别是visual studio 中的编译器和链接器 配置属性中的【c/c++】(设置编译的一些选项) 链接器】选项页中,最后的那个命令行汇总了所有生效的设置,就是最终执行的命令行 配置属性中的VC++ ...
  • 网站内部链接优化大解析

    千次阅读 2015-08-14 11:51:42
    其实内部链接是很好理解的,简单的来解释一下,内部链接和外部链接相反的,外部链接我们俗称外链,也就是我们平时在其它网站上发的外链;而内部链接呢,就是在我们自己网站上做的链接,但这些链接是内容页面之间的...
  • appium-doctor’ 不是内部外部命令,也不是可运行的程序1. appium-doctor’ 不是内部外部命令,也不是可运行的程序--问题解决2. appium 介绍 1. appium-doctor’ 不是内部外部命令,也不是可运行的程序–问题...
  • .rel.data是针对“.data”段的重定位表 在链接中,我们将函数变量统称为符号(Symbol),函数名或变量名就是符号名(Symbol Name) 符号表中的符号分类: 全局符号:定义在本目标文件的全局符号 外部符号:引用的...
  • 目标文件格式;静态链接;线程模型、运行时库
  • 动态链接

    千次阅读 2020-04-08 20:29:46
    静态链接使得不同的程序开发者部门能够相对独立的开发测试自己的程序模块,从某种意义上来讲大大促进了程序开发的效率,原先现在程序规模也随之扩大。 但静态链接的缺点也暴露出来:浪费内存、磁盘空间、模块...
  • 部分内容来自于 电子发烧友 内部总线、系统总线和外部总线汇总 部分内容来自于 知乎 前端总线,系统总线,内部总线,外部总线 本文是在两篇文章的基础上进行了二次加工,对两篇文章的精华内容进行了提炼,删掉了对...
  • 再谈Android动态链接

    千次阅读 2017-06-07 09:37:18
    前不久,我们准备将自己开发的视频播放sdk提供给公司其他部门,在打包的时候,同事问了我一个问题,为什么我们打sdk的时候需要分别提供armeabiarm64-v8a(ps,还有其他7种CPU架构)。其实这是一个常识问题,针对不同...
  • 内部和外部表 静态分区表 动态分区表 分桶表 抽样查询 1 内部和外部表 未被external修饰的是内部表(managed table),被external修饰的为外部表(external table);区别: 内部表...
  • 全局偏移表(GOT)过程链接表(PLT)

    千次阅读 2016-04-07 14:23:41
    1、全局偏移量表GOT表 ELF 格式的共享库使用 PIC 技术使代码数据的引用与地址无关,程序可以被加载到地址空间的任意位置。    PIC 在代码中的跳转分支指令不使用绝对地址。...2、对于模块外部引用的全局变量和全
  • 比如:用ST做GUI界面,面临最大的问题就是芯片内部flash最大才2M。在现在这个时代,2M其实也放不了几张图片。 故ST在某些M4 M7内核芯片上增加了QSPI外设,让用户把代码放在外部存储执行。 也同时提供了两种代码...
  • 动态链接详解

    千次阅读 2015-08-21 22:46:21
     动态链接产生最主要的原因就是静态链接空间浪费过于巨大,更重要的是现阶段各种软件都是模块化开发,不同模块都是由不同厂商开发的,一旦一个模块发生改变,整个软件就需要重新编译(静态链接的情况下)。...
  • 超硬核!操作系统学霸笔记,考试复习面试全靠它

    万次阅读 多人点赞 2021-03-22 18:43:49
    4)提供进程调度所需要的全部信息 5)实现与其他进程的同步通信 进程控制块中的信息: 进程标识符:唯一表示一个进程,有两种: 1)外部标识符:方便用户对进程的访问,通常有数字字母组成 2)内部标识符:方便...
  • 个人理解如有误,请斧正,如若更新将放到以下站点: ... 如果有内容相关的网站,欢迎给我留言,大家一起做友链。 1 内链(内部链接.
  • FPGA内部资源总结

    千次阅读 2019-07-23 11:35:19
    目前主流的FPGA仍是基于查找表技术的,已经远远超出了先前版本的基本性能,并且整合了常用功能(如RAM、时钟管理 DSP)的硬核(ASIC型)模块。如图1-1所示(注:图1-1只是一个示意图,实际上每一个系列的FPGA都有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 124,183
精华内容 49,673
关键字:

外部链接和内部链接的重要性