谈谈int*转const int* &的问题

本文深入探讨C++中指针与引用的复杂性,特别是const限定符的应用及其对指针的影响。通过实例对比不同类型的引用声明方式,揭示const在*前后位置的不同含义,并解释为何某些代码能通过编译而另一些则不行。

最近看代码遇到个指针的const引用的问题,指针真的是让人又爱又恨呐,太微妙了。指针和引用的再深入,记录分享下

情况是这样的

我是在这里遇到的,简化代码如下:

template <class T>
void show1(const T & i)
{
    // i = (T)0x555555;   编译报错
    // *i = 1314;   编译通过,效果正常
    cout << (void*)i << endl;
}
void show2(const int* & i)
{
    cout << (void*)i << endl;
}
int nTemp = 2019;
int * ptrInt = &nTemp;
show1(ptrInt);           //可以通过编译,而且我已验证确实是真正的const引用
show2(ptrInt);       //不能通过编译。cannot bind non-const lvalue reference of type 'const int*&' to an rvalue of type 'const int*'
/////////

简单来说就是这么个情况:

int nTemp = 2019;
int *ptrInt = &nTemp;
const *ptrInt2 = &nTemp;
const int* &refptrInt1 = ptrInt;   //1.这种用法会报错。你思考过吗,你觉得呢?

//下面是验证代码
const int* &refptrInt2 = ptrInt2;   //2.这个引用是const的吗
// *refptrInt2 = 999;      //error: assignment of read-only location '* refptrInt2'
refptrInt2 = (int*)0x1234;

int* const &refptrInt2 = ptrInt;    //3.其实,这个才是ptrInt的真正const引用。
*refptrInt2 = 999;
// refptrInt2 = (int*)0x1234;    //error: assignment of read-only reference 'refptrInt2' 

阐述一下

谈谈我的理解吧!
问题的关键还是在于const限定符修饰指针类型数据的神奇之处。也就是const*之前之后的差异。

像如下这种单个token的类型说明符(比如int)很好理解,

int a = 0;
const int &crefa = a;(等同表示:int const &crefa = a;)  

crefa标识符为const int引用类型,不能修改其标识符对应存储的值。

而像对于指针这种需要两个token的类型说明符(int*)的情况,就有时让人绕不过弯了。

int *pa = &a;

(int*)类型pa标识符对应的const引用应该是哪种呢?首先,其const引用,表示的效果应该也是其标识符对应存储的值不能修改。指针还不就是一个存储的值为地址的特殊变量。然后,了解了const在*前后的差异就该明白了。

int nage = 18;
const int *pt = &nage;    //a pointer to const int,不能修改指针存储的地址指向的变量的值。 解引用后内容是常量。
int* const pu = &nage;    //a const pointer to int,不能修改指针存储的地址,即改变不了指向的位置了。指针变量内容是常量。

其对应的const引用,应该这样表达:

int* const &crefpa = pa;   //和验证代码里的是一致的,这才是真正的指针的const引用想表达的意思

而不该是const int* &crefpa = pa; 其对应的该是const int* pa = &a;

而且,引用设计上还不就是指针,其实就是类似如下的伪装表示:

int a;
int & refint = a;   
int * const pint = &a;

其const就该是

int [const] &refint = a;  
int [const] * const pint = &a;  
//[const] int * const pint = &a; 同上

当为单个token,int时,其和const交换不影响编译器语义,如果是双token,int*时,不影响才怪呢。

int* [const] &refint = a;  
int* [const] * const pint = &a;  
//[const] int* * const pint = &a;  还同上吗?

顺便说下,我遇到的情况,更验证了我的说辞。
而且,模板函数实例化看来是做了特殊处理了。类型参数T,可不是简单的替换啊,是一个独立的部分了。 比如当T == int *时,const T & 和T const&,是完全一样的,不影响语义,都是int* const &可以放心用其表示const引用。

template < class T >
void show1(const T & i)
{
// i = (T)0x555555; 编译报错
// *i = 1314; 编译通过,效果正常
cout << (void*)i << endl;
}


myAvatar

&lt;think&gt;我们正在讨论字符设备驱动开发,特别是关于如何避免使用混杂设备(miscdevice)来实现驱动,并改写给定的两个函数:m74hc595_probe和m74hc595_remove。同时,我们还需要解释Linux混杂子系统的作用与机制。首先,我们需要明确:混杂设备(miscdevice)是Linux内核中提供的一种简化字符设备注册的机制。它允许驱动开发者使用一个主设备号(固定为10)来注册设备,从而省去了申请主设备号的步骤。但是,有时候我们可能希望不使用混杂设备,而是自己申请主设备号并注册字符设备。因此,问题要求我们:1.分析并改写m74hc595_probe和m74hc595_remove函数,使其不使用混杂设备。2.解释Linux混杂子系统的作用与机制。步骤:1.改写函数:不使用混杂设备,我们需要自己申请设备号,并注册字符设备。2.解释混杂子系统。参考引用:-引用[1]提到了probe函数的基本结构,用于设备初始化。-引用[2]提到i2c驱动中probe函数需要同时支持传统匹配和设备树匹配,但这里我们讨论的是字符设备,所以主要关注字符设备驱动的注册。-引用[3]是关于74HC595驱动的,我们可以参考其驱动结构。-引用[4]是DNS问题,不相关。改写函数:假设原始代码使用混杂设备,其结构可能如下:```cstaticstructmiscdevicem74hc595_miscdev={.minor=MISC_DYNAMIC_MINOR,.name=&quot;m74hc595&quot;,.fops=&amp;m74hc595_fops,};staticintm74hc595_probe(structplatform_device*pdev){intret;//...其他初始化ret=misc_register(&amp;m74hc595_miscdev);if(ret){dev_err(&amp;pdev-&gt;dev,&quot;Failedtoregistermiscdevice\n&quot;);returnret;}//...其他操作return0;}staticintm74hc595_remove(structplatform_device*pdev){misc_deregister(&amp;m74hc595_miscdev);//...其他清理return0;}```现在,我们将其改写为不使用混杂设备,而是使用标准的字符设备注册。步骤:1.定义字符设备结构体(structcdev)和文件操作结构体(structfile_operations)。2.申请设备号(可以是动态申请)。3.初始化cdev结构体,并将其与file_operations关联。4.添加cdev到系统中。5.在remove函数中注销字符设备并释放设备号。改写后的代码框架:```c#include&lt;linux/cdev.h&gt;#defineM74HC595_DEVICE_NAME&quot;m74hc595&quot;#defineM74HC595_CLASS_NAME&quot;m74hc595&quot;staticdev_tm74hc595_devt;staticstructcdevm74hc595_cdev;staticstructclass*m74hc595_class;staticintm74hc595_probe(structplatform_device*pdev){intret;structdevice*dev=&amp;pdev-&gt;dev;//1.动态申请设备号ret=alloc_chrdev_region(&amp;m74hc595_devt,0,1,M74HC595_DEVICE_NAME);if(ret&lt;0){dev_err(dev,&quot;Failedtoallocatechardeviceregion\n&quot;);returnret;}//2.初始化cdev结构体,并关联文件操作cdev_init(&amp;m74hc595_cdev,&amp;m74hc595_fops);m74hc595_cdev.owner=THIS_MODULE;//3.添加cdev到系统ret=cdev_add(&amp;m74hc595_cdev,m74hc595_devt,1);if(ret){dev_err(dev,&quot;Failedtoaddchardevice\n&quot;);gotoerr_cdev_add;}//4.创建设备节点(可选,但通常需要)m74hc595_class=class_create(THIS_MODULE,M74HC595_CLASS_NAME);if(IS_ERR(m74hc595_class)){ret=PTR_ERR(m74hc595_class);dev_err(dev,&quot;Failedtocreatedeviceclass\n&quot;);gotoerr_class_create;}device_create(m74hc595_class,NULL,m74hc595_devt,NULL,M74HC595_DEVICE_NAME);if(IS_ERR(device_create(m74hc595_class,NULL,m74hc595_devt,NULL,M74HC595_DEVICE_NAME))){dev_err(dev,&quot;Failedtocreatedevicenode\n&quot;);ret=PTR_ERR(device_create(m74hc595_class,NULL,m74hc595_devt,NULL,M74HC595_DEVICE_NAME));gotoerr_device_create;}//...其他初始化(如硬件初始化)return0;err_device_create:class_destroy(m74hc595_class);err_class_create:cdev_del(&amp;m74hc595_cdev);err_cdev_add:unregister_chrdev_region(m74hc595_devt,1);returnret;}staticintm74hc595_remove(structplatform_device*pdev){//删除设备节点device_destroy(m74hc595_class,m74hc595_devt);//销毁类class_destroy(m74hc595_class);//删除cdevcdev_del(&amp;m74hc595_cdev);//释放设备号unregister_chrdev_region(m74hc595_devt,1);//...其他清理(如释放资源)return0;}```注意:上面的代码中,文件操作结构体`m74hc595_fops`需要提前定义好,包括open,read,write,release等函数。解释混杂子系统:混杂子系统(miscdevice)是Linux内核为简化字符设备驱动开发而设计的一种机制。它使用一个固定的主设备号(10),而次设备号是动态分配的。这样,驱动开发者就不需要自己去申请主设备号,减少了代码量,也避免了主设备号冲突的问题。混杂设备的特点:1.主设备号固定为10。2.使用`misc_register`和`misc_deregister`来注册和注销设备。3.每个混杂设备对应一个`miscdevice`结构体,其中指定了设备名、文件操作等。4.混杂设备会自动在/dev下创建设备节点(如果指定了`dev`字段,则需要自己创建,但通常也可以配合自动创建)。混杂子系统的使用场景:-适用于简单的字符设备,特别是那些不需要独立主设备号的设备。-当系统中设备数量不多,且不需要独立主设备号管理时,使用混杂设备可以简化开发。混杂子系统的机制:-内核中所有混杂设备共享同一个主设备号(10),通过次设备号来区分。-混杂设备的核心是一个链表,当注册一个混杂设备时,内核会将其加入到链表中,并分配一个次设备号。-当用户空间访问设备节点(主设备号10,次设备号对应)时,内核会遍历混杂设备链表,找到对应的次设备号,然后调用其文件操作函数。总结:通过以上改写,我们避免了使用混杂设备,而是使用了标准的字符设备注册流程。同时,我们也了解了混杂子系统的作用和机制:它提供了一种便捷的字符设备注册方法,适用于简单设备驱动开发。相关问题:1.在字符设备驱动开发中,申请设备号有哪些方法?2.如何自动创建设备节点?3.混杂设备驱动和标准字符设备驱动的主要区别是什么?4.在什么情况下应该使用混杂设备?&lt;/think&gt;### 字符设备驱动开发:避免使用混杂设备的实现方法 #### 1. 改写 `m74hc595_probe` 和 `m74hc595_remove` 函数(避免混杂设备) 原始使用混杂设备的实现依赖 `miscdevice` 子系统(主设备号固定为10)。以下改写为标准字符设备驱动实现: ```c #include &lt;linux/cdev.h&gt; static dev_t dev_id; // 设备号 static struct cdev m74hc595_cdev; // 字符设备结构体 static struct class *driver_class; // 设备类 static int m74hc595_probe(struct platform_device *pdev) { int ret; // 1. 动态申请设备号 ret = alloc_chrdev_region(&amp;dev_id, 0, 1, &quot;m74hc595&quot;); if (ret &lt; 0) { dev_err(&amp;pdev-&gt;dev, &quot;Device number allocation failed\n&quot;); return ret; } // 2. 初始化字符设备 cdev_init(&amp;m74hc595_cdev, &amp;m74hc595_fops); // 关联文件操作 m74hc595_cdev.owner = THIS_MODULE; // 3. 添加设备到系统 ret = cdev_add(&amp;m74hc595_cdev, dev_id, 1); if (ret) { dev_err(&amp;pdev-&gt;dev, &quot;Char device registration failed\n&quot;); goto err_cdev; } // 4. 创建设备节点(自动生成/dev/m74hc595) driver_class = class_create(THIS_MODULE, &quot;m74hc595&quot;); device_create(driver_class, NULL, dev_id, NULL, &quot;m74hc595&quot;); // 5. 硬件初始化(参考引用[3]的74HC595驱动逻辑) // ... GPIO初始化、寄存器配置等操作 return 0; err_cdev: unregister_chrdev_region(dev_id, 1); return ret; } static int m74hc595_remove(struct platform_device *pdev) { // 1. 销毁设备节点 device_destroy(driver_class, dev_id); class_destroy(driver_class); // 2. 移除字符设备 cdev_del(&amp;m74hc595_cdev); // 3. 释放设备号 unregister_chrdev_region(dev_id, 1); // 4. 硬件资源释放 // ... 清理GPIO、内存等 return 0; } ``` #### 2. Linux 混杂子系统(Misc Subsystem)的作用与机制 **作用**: - **简化驱动开发**:为简单字符设备提供统一注册接口,避免手动管理主设备号。 - **资源复用**:共享主设备号10(`MISC_MAJOR`),次设备号动态分配[^1]。 - **适用场景**:适用于功能单一、无需独立主设备号的设备(如随机数生成器、内存控制器等)。 **核心机制**: 1. **注册流程**: ```c struct miscdevice { int minor; // 次设备号(MISC_DYNAMIC_MINOR表示动态分配) const char *name; // 设备名(自动生成/dev节点) const struct file_operations *fops; }; misc_register(&amp;my_miscdev); // 注册到内核混杂设备链表 ``` 2. **内核管理**: - 所有混杂设备通过全局链表 `misc_list` 管理 - `misc_open()` 函数根据次设备号在链表中匹配对应驱动 3. **优势与局限**: - ✅ 减少设备号冲突风险 - ✅ 自动创建设备节点 - ❌ 灵活性受限(无法自定义主设备号) - ❌ 不适合复杂设备(如需要多个次设备号的驱动) #### 3. 避免使用混杂设备的原因 1. **主设备号控制**:需要特定主设备号时(如标准设备号规范要求) 2. **多设备管理**:当单个驱动需管理多个设备节点时(混杂设备通常1:1映射) 3. **特殊需求**: - 需要实现设备号范围分配 - 与现有驱动框架深度集成(如结合platform总线时需直接操作字符设备[^2]) - 性能敏感场景(减少抽象层开销) &gt; **关键区别**:标准字符设备驱动需手动管理设备号生命周期(`alloc_chrdev_region/cdev_add`),而混杂设备通过 `misc_register` 封装这些操作[^1]。 #### 4. 驱动开发建议 - **简单设备**:优先使用混杂设备(减少代码量) - **复杂/高性能设备**:采用标准字符设备实现 - **设备树兼容**:无论是否使用混杂设备,都应支持设备树匹配机制[^2] - **错误处理**:在 `probe` 中实现资源回滚(如改写示例中的 `goto err_cdev`) --- ### 相关问题 1. 字符设备驱动中 `cdev_init` 和 `cdev_add` 函数的具体作用是什么? 2. 如何通过设备树为字符设备驱动指定硬件资源? 3. 混杂设备驱动与标准字符设备驱动的性能差异如何量化? 4. 为什么在 Linux 驱动开发中需要实现 `probe` 和 `remove` 的对称资源管理? 5. 当驱动需要支持多个相同硬件设备时,应如何扩展字符设备注册逻辑? [^1]: 混杂设备通过统一主设备号简化注册流程,适用于简单外设驱动。 [^2]: 设备树匹配需同时实现 `of_match_table` 和传统 ID 表以确保兼容性。 [^3]: 74HC595 等串行器件需注意时序控制,通常在 `probe` 中初始化 GPIO 和时钟。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值