[转]深入 char * ,char ** ,char a[ ] ,char *a[] 内核

本文深入探讨了C语言中指针和数组的概念,包括它们的基本特性、操作方式及常见误区,特别关注字符数组与字符指针的区别,以及二级指针的应用。
                    版权声明:本文为博主原创文章,未经博主允许不得转载。                        https://blog.youkuaiyun.com/daiyutage/article/details/8604720                    </div>
                                                <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-3019150162.css">
                                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-3019150162.css">
            <div class="htmledit_views" id="content_views">

   C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用,这些导致了许多指针和数组的迷惑,因此,刻意再次深入探究了指针和数组这玩意儿,其他类型的数组比较简单,容易混淆的是字符数组和字符指针这两个。。。下面就开始剖析一下这两位的恩怨情仇。。。

 1 数组的本质

   数组是多个元素的集合,在内存中分布在地址相连的单元中,所以可以通过其下标访问不同单元的元素。。

 2 指针。

   指针也是一种变量,只不过它的内存单元中保存的是一个标识其他位置的地址。。由于地址也是整数,在32位平台下,指针默认为32位。。

 3 指针的指向?

   指向的直接意思就是指针变量所保存的其他的地址单元中所存放的数据类型。

   int  * p ;//p 变量保存的地址所在内存单元中的数据类型为整型

           float *q;// ........................................浮点型

           不论指向的数据类型为那种,指针变量其本身永远为整型,因为它保存的地址。

    4  字符数组。。。

        字面意思是数组,数组中的元素是字符。。确实,这就是它的本质意义。

         char  str[10]; 

         定义了一个有十个元素的数组,元素类型为字符。

         C语言中定义一个变量时可以初始化。

         char  str[10] = {"hello world"};

         当编译器遇到这句时,会把str数组中从第一个元素把hello world\0 逐个填入。。

         由于C语言中没有真正的字符串类型,可以通过字符数组表示字符串,因为它的元素地址是连续的,这就足够了。

         C语言中规定数组代表数组所在内存位置的首地址,也是 str[0]的地址,即str = &str[0];

         而printf("%s",str); 为什么用首地址就可以输出字符串。。

          因为还有一个关键,在C语言中字符串常量的本质表示其实是一个地址,这是许多初学者比较难理解的问题。。。

          举例:

          char  *s ;

          s = "China";

          为什么可以把一个字符串赋给一个指针变量。。

          这不是类型不一致吗???

          这就是上面提到的关键 。。

          C语言中编译器会给字符串常量分配地址,如果 "China", 存储在内存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .

          s = "China" ,意识是什么,对了,地址。

          其实真正的意义是 s ="China" = 0x3000;

          看清楚了吧 ,你把China 看作是字符串,但是编译器把它看作是地址 0x3000,即字符串常量的本质表现是代表它的第一个字符的地址。。。。。。。。。。

          s = 0x3000

          这样写似乎更符合直观的意思。。。

          搞清楚这个问题。。

          那么 %s ,它的原理其实也是通过字符串首地址输出字符串,printf("%s ", s);   传给它的其实是s所保存的字符串的地址。。。

          比如

        

  1. #include <stdio.h>
  2. int main()
  3. {
  4. char *s;
  5. s = "hello";
  6. printf("%p\n",s);
  7. return 0;
  8. }

                          

          

 

      可以看到 s = 0x00422020 ,这也是"China"的首地址

      所以,printf("%s",0x00422020);也是等效的。。

     

       字符数组:

       char  str[10] = "hello";

       前面已经说了,str = &str[0] , 也等于 "hello"的首地址。。

       所以printf("%s",str); 本质也是 printf("%s", 地址");

        C语言中操作字符串是通过它在内存中的存储单元的首地址进行的,这是字符串的终极本质。。。

    5  char *  与 char  a[ ];

       char  *s;

       char  a[ ] ;

       前面说到 a代表字符串的首地址,而s 这个指针也保存字符串的地址(其实首地址),即第一个字符的地址,这个地址单元中的数据是一个字符,

   这也与 s 所指向的 char 一致。

      因此可以 s = a;

       但是不能 a = s;

       C语言中数组名可以复制给指针表示地址, 但是却不能赋给给数组名,它是一个常量类型,所以不能修改。。

       当然也可以这样:
       

  1. char a [ ] = "hello";
  2. char *s =a;
  3. for(int i= 0; i < strlen(a) ; i++)
  4. printf("%c", s[i]);
  5. printf("%c",*s++);

        字符指针可以用 间接操作符 *取其内容,也可以用数组的下标形式 [ ],数组名也可以用 *操作,因为它本身表示一个地址 。。

       比如 printf("%c",*a);  将会打印出 'h'

       char * 与 char a[ ] 的本质区别:

       当定义 char a[10 ]  时,编译器会给数组分配十个单元,每个单元的数据类型为字符。。

       而定义 char *s 时,  这是个指针变量,只占四个字节,32位,用来保存一个地址。。

       sizeof(a) = 10 ;

       sizeof(s)  = ?

       当然是4了,编译器分配4个字节32位的空间,这个空间中将要保存地址。。。

        printf("%p",s);

        这个表示 s 的单元中所保存的地址。。

        printf("%p",&s);

        这个表示变量本身所在内存单元地址。。。。,不要搞混了。。

        用一句话来概括,就是 char *s 只是一个保存字符串首地址的指针变量, char a[ ] 是许多连续的内存单元,单元中的元素为char ,之所以用 char *能达到

 char a  [ ] 的效果,还是字符串的本质,地址,即给你一个字符串地址,便可以随心所欲的操所他。。但是,char* 和 char a[ ] 的本质属性是不一样的。。

    

     6      char **  与char  * a[ ] ;

            先看 char  *a [ ] ;

            由于[ ] 的优先级高于* 所以a先和 [ ]结合,他还是一个数组,数组中的元素才是char * ,前面讲到char * 是一个变量,保存的地址。。

            所以 char *a[ ] = {"China","French","America","German"};

            同过这句可以看到, 数组中的元素是字符串,那么sizeof(a) 是多少呢,有人会想到是五个单词的占内存中的全部字节数 6+7+8+7 = 28;

            但是其实sizeof(a) = 16;

            为什么,前面已经说到, 字符串常量的本质是地址,a 数组中的元素为char * 指针,指针变量占四个字节,那么四个元素就是16个字节了

            看一下实例:

        

  1. #include <stdio.h>
  2.    int main()
  3.    {
  4.    char *a [ ] = {"China","French","America","German"};
  5.    printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]);
  6.    return 0;
  7.    }

    

      可以看到数组中的四个元素保存了四个内存地址,这四个地址中就代表了四个字符串的首地址,而不是字符串本身。。。

      因此sizeof(a)当然是16了。。

      注意这四个地址是不连续的,它是编译器为"China","French","America","German" 分配的内存空间的地址, 所以,四个地址没有关联。

       

  1. #include <stdio.h>
  2.    int main()
  3.    {
  4.    char *a [ ] = {"China","French","America","German"};
  5. printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]); //数组元素中保存的地址
  6.    printf("%p %p %p %p\n",&a[0],&a[1],&a[2],&a[3]);//数组元素单元本身的地址
  7.    return 0;
  8.    }

          

      可以看到 0012FF38 0012FF3C 0012FF40 0012FF44,这四个是元素单元所在的地址,每个地址相差四个字节,这是由于每个元素是一个指针变量占四个字节。。。

       char **s;

       char **为二级指针, s保存一级指针 char *的地址,关于二级指针就在这里不详细讨论了 ,简单的说一下二级指针的易错点。  

       举例:

       

  1. char *a [ ] = {"China","French","America","German"};
  2. char **s = a;

       为什么能把 a赋给s,因为数组名a代表数组元素内存单元的首地址,即 a = &a[0] = 0012FF38;

       而 0x12FF38即 a[0]中保存的又是 00422FB8 ,这个地址, 00422FB8为字符串"China"的首地址。

       即 *s = 00422FB8 = "China";

         这样便可以通过s 操作 a 中的数据

      

  1. printf("%s",*s);
  2. printf("%s",a[0]);
  3. printf("%s",*a);

      都是一样的。。。

      但还是要注意,不能a = s,前面已经说到,a 是一个常量。。

      再看一个易错的点:

      

  char **s = "hello world";

      这样是错误的,

       因为  s 的类型是 char **  而 "hello world "的类型是 char *

       虽然都是地址, 但是指向的类型不一样,因此,不能这样用。,从其本质来分析,"hello world",代表一个地址,比如0x003001,这个地址中的内容是 'h'

  ,为 char 型,而 s 也保存一个地址 ,这个地址中的内容(*s) 是char * ,是一个指针类型, 所以两者类型是不一样的。 。。

  如果是这样呢?
    

  1.   char **s;
  2. *s = "hello world";

       貌似是合理的,编译也没有问题,但是 printf("%s",*s),就会崩溃

       why??

      咱来慢慢推敲一下。。

       printf("%s",*s); 时,首先得有s 保存的地址,再在这个地址中找到 char *  的地址,即*s;

      举例:

      

   s = 0x1000;

      在0x1000所在的内存单元中保存了"hello world"的地址 0x003001 , *s = 0x003001;

      这样printf("%s",*s);

      这样会先找到 0x1000,然后找到0x003001;

      如果直接 char  **s;

      

   *s = "hello world";

       s 变量中保存的是一个无效随机不可用的地址, 谁也不知道它指向哪里。。。。,*s 操作会崩溃。。

       所以用 char **s 时,要给它分配一个内存地址。

      

  1. char **s ;
  2. s = (char **) malloc(sizeof(char**));
  3. *s = "hello world";

      这样 s 给分配了了一个可用的地址,比如 s = 0x412f;

      然后在 0x412f所在的内存中的位置,保存 "hello world"的值。。

    再如:

   

  1. #include <stdio.h>
  2. void buf( char **s)
  3. {
  4. *s = "message";
  5. }
  6. int main()
  7. {
  8. char *s ;
  9. buf(&s);
  10. printf("%s\n",s);
  11. }

    二级指针的简单用法。。。。,说白了,二级指针保存的是一级指针的地址,它的类型是指针变量,而一级指针保存的是指向数据所在的内存单元的地址,虽然都是地址,但是类型是不一样的。。。

     

     

   

 

      

     

      

      

    

    

     

       

 

     

     

      

   

   

 

 

          

            

       

      

      

 

 

 

 

 

            

 

 

 

 

 

         

         

  

         

        

        

 

 

 

 

 

 

                      

<think>嗯,用户问的是如何将`long viraddr`换成`char*`格式。首先,我需要确定用户使用的编程语言,因为这在C和C++中可能有不同的处理方式。不过通常这类问题出现在C语言中,尤其是在处理底层内存操作时,比如操作系统开发或嵌入式系统编程。 接下来,用户提到的`long viraddr`,这里`viraddr`可能代表虚拟地址(virtual address),也就是一个长整型的地址值,需要换为`char*`类型的指针。这在需要直接操作内存地址的时候很常见,比如访问特定内存位置的数据,或者进行类型换以便按字节处理内存。 首先,我需要考虑类型换的正确性和安全性。在C语言中,将整数换为指针通常使用强制类型换,但需要注意不同平台下指针和整数的大小是否一致。例如,在32位系统中,`long`可能足够存放指针,但在64位系统中,`long`可能是4字节或8字节,而指针通常是8字节,这时候使用`long`可能不够,应该使用`uintptr_t`或者`intptr_t`这类定义明确的类型,以确保足够的宽度。 所以,用户使用`long`可能存在潜在的问题,尤其是在64位环境下。不过假设用户的环境中`long`足够存放指针,比如某些平台的`long`是64位的,那么直接强制换是可行的。但需要提醒用户注意可移植性问题。 换的方法通常是使用强制类型换操作符,例如`(char*)viraddr`,但需要注意`viraddr`本身是一个整数类型,所以正确的写法应该是`(char*) (uintptr_t) viraddr`,先将`long`换为`uintptr_t`,再为指针,这样可以确保类型宽度正确。不过用户的问题是直接换`long`到`char*`,所以可能直接写`(char*)viraddr`,但需要指出潜在的问题。 另外,需要考虑对齐问题。将任意整数换为指针可能导致未对齐的访问,这在某些架构上会引起错误或性能下降。因此,需要确保地址是对齐的,特别是在访问特定类型的数据时。 还需要提醒用户,直接操作虚拟地址属于底层操作,可能存在风险,比如访问未分配的内存导致段错误,或者破坏程序的其他部分。因此,在实际应用中需要谨慎,并确保地址的合法性。 此外,用户可能需要这样的换来进行内存的字节级操作,比如处理二进制数据、缓冲区等。这种情况下,使用`char*`是合适的,因为`char`在C中定义为1字节,可以逐字节访问内存。 总结步骤应该是:首先将`long`类型的地址换为`uintptr_t`以确保正确的宽度,然后再换为`char*`,例如: ```c char* ptr = (char*)((uintptr_t)viraddr); ``` 但根据用户的具体情况,如果环境允许,可能直接使用强制换: ```c char* ptr = (char*)viraddr; ``` 同时,需要强调类型安全性和可移植性的注意事项,避免在跨平台时出现问题。 可能用户遇到的错误是直接将`long`换为指针而没有考虑类型宽度,导致高位截断或符号扩展的问题。例如,在64位系统上,如果`long`是4字节而指针是8字节,换会丢失高位的地址信息,导致错误。因此,使用`uintptr_t`或`intptr_t`会更安全。 最后,需要提醒用户,这类换应尽量避免,除非确实需要直接操作内存地址。在高级应用中,应使用更安全的内存管理方法,而不是直接处理指针的数值形式。</think>--- ### 一、问题核心分析 将 `long` 类型的虚拟地址 `viraddr` 换为 `char*` 指针时,需要处理以下关键问题: 1. **类型宽度匹配**:确保 `long` 的位数足以存储地址(例如在64位系统中,`long` 可能只有4字节,而地址需要8字节) 2. **符号扩展风险**:`long` 是有符号类型,直接换可能导致错误的高位填充 3. **可移植性**:不同平台对 `long` 和指针的宽度定义不同 --- ### 二、正确换方法 #### 步骤1:优先使用 `uintptr_t`(推荐方案) ```c #include <stdint.h> // 必须包含此头文件 long viraddr = 0x7FFF0000; // 示例地址值 char* p = (char*)((uintptr_t)viraddr); // 先为无符号整数类型 ``` - **优点**: - `uintptr_t` 是C99标准定义的无符号整数类型,**保证与指针宽度一致** - 避免符号扩展问题 #### 步骤2:直接强制换(需谨慎) ```c char* p = (char*)viraddr; // 仅当明确知晓 long 与指针宽度匹配时使用 ``` - **风险提示**: - 在64位Linux/macOS中,`long` 为8字节,Windows中为4字节 - 若 `long` 宽度小于指针宽度,会丢失高位地址数据 --- ### 三、跨平台安全实践 #### 场景1:明确需要兼容不同平台 ```c #if defined(_WIN64) || defined(__LP64__) // 64位系统使用 unsigned long long unsigned long long addr_value = viraddr; #else // 32位系统使用 unsigned long unsigned long addr_value = viraddr; #endif char* p = (char*)addr_value; ``` #### 场景2:使用编译器扩展属性(GCC/Clang) ```c long viraddr __attribute__((aligned(8))) = 0x7FFF0000; // 强制对齐 char* p = (char*)viraddr; ``` --- ### 四、关键注意事项 1. **地址合法性验证**: ```c if ((uintptr_t)viraddr % sizeof(void*) != 0) { // 处理地址未对齐错误 } ``` 2. **内存访问权限**: - 换后的指针可能指向无访问权限的区域,需配合系统API(如`mprotect`)设置权限 3. **调试技巧**: ```c printf("原始地址: 0x%lx\n", (unsigned long)viraddr); printf("换后指针: %p\n", (void*)p); // 使用%p格式化输出指针 ``` --- ### 五、错误示例分析 #### 错误1:忽略整数符号 ```c long viraddr = -1; // 负数地址 char* p = (char*)viraddr; // 换为0xFFFFFFFFFFFFFFFF(错误地址) ``` - **修复**:使用无符号类型存储地址 #### 错误2:混合指针与整数运算 ```c long viraddr = 0x1000; char* p = (char*)viraddr + 5; // 语法合法但可能产生未定义行为 ``` - **规范写法**: ```c uintptr_t tmp = (uintptr_t)viraddr; char* p = (char*)(tmp + 5); ``` --- ### 六、进阶应用:内存映射操作 ```c #include <sys/mman.h> // 将物理地址映射到用户空间 long phys_addr = 0xFEEDBEEF; // 示例物理地址 void* vaddr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, // 打开的设备文件描述符 phys_addr); char* p = (char*)vaddr; // 安全换为可操作的char指针 ``` --- ### 七、总结对比表 | 方法 | 优点 | 缺点 | 适用场景 | |---------------------|--------------------------|-----------------------|---------------------| | `uintptr_t`换 | 类型安全,标准支持 | 需要C99+ | 跨平台通用代码 | | 直接强制换 | 代码简洁 | 有移植风险 | 单一目标平台开发 | | 编译器属性对齐 | 确保内存访问安全 | 依赖特定编译器 | 嵌入式/驱动开发 | | 系统API映射 | 合法访问物理内存 | 需要内核权限 | 硬件操作/内核模块 | 建议优先使用 `uintptr_t` 方案,并在关键位置添加静态断言验证类型宽度: ```c _Static_assert(sizeof(uintptr_t) == sizeof(void*), "uintptr_t与指针宽度不匹配"); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值