Mad Pointer in C

本文探讨了C语言中void*指针的特殊性质及其应用。void*指针可以作为中间媒介,实现不同数据类型间的转换。文章还讨论了如何利用这一特性实现面向对象编程。

        在C语言中,变量variable的类型是定义得非常死板的。没有一个特殊类型的变量能被强制转换成其他的类型。或者任意类型的变量能直接强制转换成为一个特殊类型。的确,这样的特殊类型在C语言中是不存在的。

        但是,我们可以找到一个特殊的指针去实现这样万能的转换。这样特殊的使命就交给一个最常用的,但是却最不理解的类型void*。没错,void是一个特殊的类型。说他特殊是因为你不能只样去定义 void a; 这样定义要不你不能使用这样变量b(TC2),要不就直接是编译出错(C-FREE)。实在是相当令人琢磨不透的东西。但是void类型的指针却是一个非常usefull的pointer。我们看下面一个例子:

#include "stdio.h"
int main(void){
    void *p = NULL;
    int a = 100;
        p = &a;//任何类型的指针都可以转换为void*
        printf("%d",*(int*)p);//输出的时候再强制转换回来
}

不知道路过的朋友看到这里会不会了解这样的功能究竟有什么用~我想大家都能猜测得到了,我将要这个特殊的指针去实现一般C语言实现不了的东西----面向对象。

我在学Java的时候突然领悟到,那些所谓在Java中的类其实不过都是一些结构体,Java中的类的创建、调用、传递、销毁、继承、接口等等操作无非都是对指针进行操作。既然C语言中也有指针,那为什么我不能用C语言来实现面向对象呢?OO In C

当然有些人会说,用一种语言去实现另外一种语言这不是很无聊吗?非也非也,面向对象和面向结构的区别仅仅在于是表现形式的不同。引用一句经典的话来说就是,石墨和钻石都是由同一种元素构成的,但是碳原子之间的组成方式不一样,表现就完全不一样了。此话甚为经典。

[ 7.744965][ T744] Hardware name: QEMU KVM Virtual Machine, BIOS 0.0.0 02/06/2015 [ 7.746456][ T744] pstate: 604000c5 (nZCv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 7.747989][ T744] pc : __pi_memcpy_generic+0x2c/0x230d169f63-dc54-4b8a-93a9-78671f86a1ed. [ 7.750093][ T744] lr : vrdma_post_recv+0x11c/0x248 [vrdma] [ 7.751394][ T744] sp : ffff8000856c35c0 [ 7.751923][ T744] x29: ffff8000856c35c0 x28: 0000000000000000 x27: 0000000000000000 [ 7.752932][ T744] x26: 0000000000000010 x25: ffff8000856c3610 x24: 0000000000000000 [ 7.753965][ T744] x23: ffff8000856c3680 x22: 0000000000000010 x21: ffff0000d9f14480 [ OK ] Mounted /boot. [ 7.754908][ T744] x20: ffff8000856c3698 x19: ffff0003eaa02a00 x18: 00000000ffffffff [ 7.756466][ T744] x17: 0000000000001400 x16: 00000000ffffffff x15: ffff8000856c3038 Mounting /boot/efi... [ 7.757894][ T744] x14: ffff0003eab9350f x13: 0000016000000128 x12: 000000042aa000c0 [ 7.758918][ T744] x11: 0000000000000001 x10: 0000000000000001 x9 : ffff80007b26ad04 [ 7.760348][ T744] x8 : ffff0000d9f144c0 x7 : 0000016000000128 x6 : 000000042aa000c0 [ 7.761706][ T744] x5 : 0000000000000010 x4 : ffff8000856c3698 x3 : 00000000000004b2 [ 7.763084][ T744] x2 : 0000000000000010 x1 : ffff8000856c3688 x0 : 0000000000000000 [ 7.764084][ T744] Call trace: [ 7.765006][ T744] __pi_memcpy_generic+0x2c/0x230 (P) [ 7.766103][ T744] ib_mad_post_receive_mads+0x188/0x428 [ib_core] [ 7.767357][ T744] ib_mad_port_start+0x158/0x250 [ib_core] [ 7.768508][ T744] ib_mad_port_open+0x228/0x4e0 [ib_core] [ 7.769662][ T744] ib_mad_init_device+0x70/0x1f8 [ib_core] [ 7.770823][ T744] add_client_context+0x13c/0x208 [ib_core] [ 7.771960][ T744] enable_device_and_get+0xd4/0x1b8 [ib_core] [ 7.773140][ T744] ib_register_device.part.0+0x10c/0x288 [ib_core] [ 7.774526][ T744] ib_register_device+0x34/0x50 [ib_core] [ 7.775623][ T744] vrdma_register_ib_device+0xb4/0x140 [vrdma] [ 7.776882][ T744] vrdma_probe+0x98/0x150 [vrdma] [ 7.777927][ T744] virtio_dev_probe+0x1a4/0x280 [virtio] [ 7.778941][ T744] really_probe+0xc8/0x3a0 [ 7.780008][ T744] __driver_probe_device+0x84/0x170 [ 7.780759][ T744] driver_probe_device+0x44/0x120 [ 7.781902][ T744] __driver_attach+0xfc/0x210 [ 7.782855][ T744] bus_for_each_dev+0x7c/0xe8 [ 7.783808][ T744] driver_attach+0x2c/0x40 [ 7.784726][ T744] bus_add_driver+0x118/0x248 [ 7.785668][ T744] driver_register+0x68/0x138 [ 7.786595][ T744] __register_virtio_driver+0x2c/0x50 [virtio] [ 7.787737][ T744] vrdma_init+0x50/0xff8 [vrdma] [ 7.788692][ T744] do_one_initcall+0x4c/0x2b8 [ 7.789631][ T744] do_init_module+0x60/0x278 [ 7.790765][ T744] load_module+0x84c/0x908 [ 7.791601][ T744] init_module_from_file+0x8c/0xd8 [ 7.792524][ T744] idempotent_init_module+0x134/0x358 [ 7.793480][ T744] __arm64_sys_finit_module+0x74/0xe8 [ 7.794410][ T744] invoke_syscall.constprop.0+0x58/0xf8 [ 7.795378][ T744] do_el0_svc+0xb0/0xd0
09-29
/* Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file This file is part of 0MQ. 0MQ is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 0MQ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/ >. */ #ifndef __ZMQ_YPIPE_HPP_INCLUDED__ #define __ZMQ_YPIPE_HPP_INCLUDED__ #include "atomic_ptr.hpp" #include "yqueue.hpp" // Lock-free queue implementation. // Only a single thread can read from the pipe at any specific moment. // Only a single thread can write to the pipe at any specific moment. // T is the type of the object in the queue. // N is granularity of the pipe, i.e. how many items are needed to // perform next memory allocation. template <typename T, int N> class ypipe_t { public: // Initialises the pipe. inline ypipe_t() { // Insert terminator element into the queue. queue.push(); //yqueue_t的尾指针加1,开始back_chunk为空,现在back_chunk指向第一个chunk_t块的第一个位置 // Let all the pointers to point to the terminator. // (unless pipe is dead, in which case c is set to NULL). r = w = f = &queue.back(); //就是让r、w、f、c四个指针都指向这个end迭代器 c.set(&queue.back()); } // The destructor doesn't have to be virtual. It is mad virtual // just to keep ICC and code checking tools from complaining. inline virtual ~ypipe_t() { } // Following function (write) deliberately copies uninitialised data // when used with zmq_msg. Initialising the VSM body for // non-VSM messages won't be good for performance. #ifdef ZMQ_HAVE_OPENVMS #pragma message save #pragma message disable(UNINIT) #endif // Write an item to the pipe. Don't flush it yet. If incomplete is // set to true the item is assumed to be continued by items // subsequently written to the pipe. Incomplete items are neverflushed down the stream. // 写入数据,incomplete参数表示写入是否还没完成,在没完成的时候不会修改flush指针,即这部分数据不会让读线程看到。 inline void write(const T &value_, bool incomplete_) { // Place the value to the queue, add new terminator element. queue.back() = value_; queue.push(); // Move the "flush up to here" poiter. if (!incomplete_) { f = &queue.back(); // 记录要刷新的位置 // printf("1 f:%p, w:%p\n", f, w); } else { // printf("0 f:%p, w:%p\n", f, w); } } #ifdef ZMQ_HAVE_OPENVMS #pragma message restore #endif // Pop an incomplete item from the pipe. Returns true is such // item exists, false otherwise. inline bool unwrite(T *value_) { if (f == &queue.back()) return false; queue.unpush(); *value_ = queue.back(); return true; } // Flush all the completed items into the pipe. Returns false if // the reader thread is sleeping. In that case, caller is obliged to // wake the reader up before using the pipe again. // 刷新所有已经完成的数据到管道,返回false意味着读线程在休眠,在这种情况下调用者需要唤醒读线程。 // 批量刷新的机制, 写入批量后唤醒读线程; // 反悔机制 unwrite inline bool flush() { // If there are no un-flushed items, do nothing. if (w == f) // 不需要刷新,即是还没有新元素加入 return true; // Try to set 'c' to 'f'. // read时如果没有数据可以读取则c的值会被置为NULL if (c.cas(w, f) != w) // 尝试将c设置为f,即是准备更新w的位置 { // Compare-and-swap was unseccessful because 'c' is NULL. // This means that the reader is asleep. Therefore we don't // care about thread-safeness and update c in non-atomic // manner. We'll return false to let the caller know // that reader is sleeping. c.set(f); // 更新为新的f位置 w = f; return false; //线程看到flush返回false之后会发送一个消息给读线程,这需要写业务去做处理 } else // 读端还有数据可读取 { // Reader is alive. Nothing special to do now. Just move // the 'first un-flushed item' pointer to 'f'. w = f; // 更新f的位置 return true; } } // Check whether item is available for reading. // 这里面有两个点,一个是检查是否有数据可读,一个是预取 inline bool check_read() { // Was the value prefetched already? If so, return. if (&queue.front() != r && r) //判断是否在前几次调用read函数时已经预取数据了return true; return true; // There's no prefetched value, so let us prefetch more values. // Prefetching is to simply retrieve the // pointer from c in atomic fashion. If there are no // items to prefetch, set c to NULL (using compare-and-swap). // 两种情况 // 1. 如果c值和queue.front(), 返回c值并将c值置为NULL,此时没有数据可读 // 2. 如果c值和queue.front(), 返回c值,此时可能有数据度的去 r = c.cas(&queue.front(), NULL); //尝试预取数据 // If there are no elements prefetched, exit. // During pipe's lifetime r should never be NULL, however, // it can happen during pipe shutdown when items are being deallocated. if (&queue.front() == r || !r) //判断是否成功预取数据 return false; // There was at least one value prefetched. return true; } // Reads an item from the pipe. Returns false if there is no value. // available. inline bool read(T *value_) { // Try to prefetch a value. if (!check_read()) return false; // There was at least one value prefetched. // Return it to the caller. *value_ = queue.front(); queue.pop(); return true; } // Applies the function fn to the first elemenent in the pipe // and returns the value returned by the fn. // The pipe mustn't be empty or the function crashes. inline bool probe(bool (*fn)(T &)) { bool rc = check_read(); // zmq_assert(rc); return (*fn)(queue.front()); } protected: // Allocation-efficient queue to store pipe items. // Front of the queue points to the first prefetched item, back of // the pipe points to last un-flushed item. Front is used only by // reader thread, while back is used only by writer thread. yqueue_t<T, N> queue; // Points to the first un-flushed item. This variable is used // exclusively by writer thread. T *w; //指向第一个未刷新的元素,只被写线程使用 // Points to the first un-prefetched item. This variable is used // exclusively by reader thread. T *r; //指向第一个还没预提取的元素,只被读线程使用 // Points to the first item to be flushed in the future. T *f; //指向下一轮要被刷新的一批元素中的第一个 // The single point of contention between writer and reader thread. // Points past the last flushed item. If it is NULL, // reader is asleep. This pointer should be always accessed using // atomic operations. atomic_ptr_t<T> c; //读写线程共享的指针,指向每一轮刷新的起点(看代码的时候会详细说)。当c为空时,表示读线程睡眠(只会在读线程中被设置为空) // Disable copying of ypipe object. ypipe_t(const ypipe_t &); const ypipe_t &operator=(const ypipe_t &); }; #endif /* Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file This file is part of 0MQ. 0MQ is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 0MQ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/ >. */ #ifndef __ZMQ_YQUEUE_HPP_INCLUDED__ #define __ZMQ_YQUEUE_HPP_INCLUDED__ #include <stdlib.h> #include <stddef.h> // #include "err.hpp" #include "atomic_ptr.hpp" // yqueue is an efficient queue implementation. The main goal is // to minimise number of allocations/deallocations needed. Thus yqueue // allocates/deallocates elements in batches of N. // // yqueue allows one thread to use push/back function and another one // to use pop/front functions. However, user must ensure that there's no // pop on the empty queue and that both threads don't access the same // element in unsynchronised manner. // // T is the type of the object in the queue. 队列中元素的类型 // N is granularity(粒度) of the queue (how many pushes have to be done till actual memory allocation is required). // 即是yqueue_t一个结点可以装载N个T类型的元素, yqueue_t的一个结点是一个数组 template <typename T, int N> class yqueue_t { public: // 创建队列. inline yqueue_t() { begin_chunk = (chunk_t *)malloc(sizeof(chunk_t)); alloc_assert(begin_chunk); begin_pos = 0; back_chunk = NULL; //back_chunk总是指向队列中最后一个元素所在的chunk,现在还没有元素,所以初始为空 back_pos = 0; end_chunk = begin_chunk; //end_chunk总是指向链表的最后一个chunk end_pos = 0; } // 销毁队列. inline ~yqueue_t() { while (true) { if (begin_chunk == end_chunk) { free(begin_chunk); break; } chunk_t *o = begin_chunk; begin_chunk = begin_chunk->next; free(o); } chunk_t *sc = spare_chunk.xchg(NULL); free(sc); } // Returns reference to the front element of the queue. // If the queue is empty, behaviour is undefined. // 返回队列头部元素的引用,调用者可以通过该引用更新元素,结合pop实现出队列操作。 inline T &front() // 返回的是引用,是个左值,调用者可以通过其修改容器的值 { return begin_chunk->values[begin_pos]; } // Returns reference to the back element of the queue. // If the queue is empty, behaviour is undefined. // 返回队列尾部元素的引用,调用者可以通过该引用更新元素,结合push实现插入操作。 // 如果队列为空,该函数是不允许被调用。 inline T &back() // 返回的是引用,是个左值,调用者可以通过其修改容器的值 { return back_chunk->values[back_pos]; } // Adds an element to the back end of the queue. inline void push() { back_chunk = end_chunk; back_pos = end_pos; // if (++end_pos != N) //end_pos!=N表明这个chunk节点还没有满 return; chunk_t *sc = spare_chunk.xchg(NULL); // 为什么设置为NULL? 因为如果把之前值取出来了则没有spare chunk了,所以设置为NULL if (sc) // 如果有spare chunk则继续复用它 { end_chunk->next = sc; sc->prev = end_chunk; } else // 没有则重新分配 { // static int s_cout = 0; // printf("s_cout:%d\n", ++s_cout); end_chunk->next = (chunk_t *)malloc(sizeof(chunk_t)); // 分配一个chunk alloc_assert(end_chunk->next); end_chunk->next->prev = end_chunk; } end_chunk = end_chunk->next; end_pos = 0; } // Removes element from the back end of the queue. In other words // it rollbacks last push to the queue. Take care: Caller is // responsible for destroying the object being unpushed. // The caller must also guarantee that the queue isn't empty when // unpush is called. It cannot be done automatically as the read // side of the queue can be managed by different, completely // unsynchronised thread. // 必须要保证队列不为空,参考ypipe_t的uwrite inline void unpush() { // First, move 'back' one position backwards. if (back_pos) // 从尾部删除元素 --back_pos; else { back_pos = N - 1; // 回退到前一个chunk back_chunk = back_chunk->prev; } // Now, move 'end' position backwards. Note that obsolete end chunk // is not used as a spare chunk. The analysis shows that doing so // would require free and atomic operation per chunk deallocated // instead of a simple free. if (end_pos) // 意味着当前的chunk还有其他元素占有 --end_pos; else { end_pos = N - 1; // 当前chunk没有元素占用,则需要将整个chunk释放 end_chunk = end_chunk->prev; free(end_chunk->next); end_chunk->next = NULL; } } // Removes an element from the front end of the queue. inline void pop() { if (++begin_pos == N) // 删除满一个chunk才回收chunk { chunk_t *o = begin_chunk; begin_chunk = begin_chunk->next; begin_chunk->prev = NULL; begin_pos = 0; // 'o' has been more recently used than spare_chunk, // so for cache reasons we'll get rid of the spare and // use 'o' as the spare. chunk_t *cs = spare_chunk.xchg(o); //由于局部性原理,总是保存最新的空闲块而释放先前的空闲快 free(cs); } } private: // Individual memory chunk to hold N elements. // 链表结点称之为chunk_t struct chunk_t { T values[N]; //每个chunk_t可以容纳N个T类型的元素,以后就以一个chunk_t为单位申请内存 chunk_t *prev; chunk_t *next; }; // Back position may point to invalid memory if the queue is empty, // while begin & end positions are always valid. Begin position is // accessed exclusively be queue reader (front/pop), while back and // end positions are accessed exclusively by queue writer (back/push). chunk_t *begin_chunk; // 链表头结点 int begin_pos; // 起始点 chunk_t *back_chunk; // 队列中最后一个元素所在的链表结点 int back_pos; // 尾部 chunk_t *end_chunk; // 拿来扩容的,总是指向链表的最后一个结点 int end_pos; // People are likely to produce and consume at similar rates. In // this scenario holding onto the most recently freed chunk saves // us from having to call malloc/free. atomic_ptr_t<chunk_t> spare_chunk; //空闲块(把所有元素都已经出队的块称为空闲块),读写线程的共享变量 // Disable copying of yqueue. yqueue_t(const yqueue_t &); const yqueue_t &operator=(const yqueue_t &); }; #endif #include <atomic> #include <pthread.h> #include <mutex> #include <condition_variable> // 全局计数器 extern int s_queue_item_num; // 每个生产者要插入的元素数 extern int s_producer_thread_num; extern int s_consumer_thread_num; extern int s_count_push; // 全局序号生成器 extern int s_count_pop; // 全局消费计数 /* 原子加:返回加后的值 */ static int lxx_atomic_add(int *ptr, int increment) { int old_value = *ptr; __asm__ volatile("lock; xadd %0, %1 \n\t" : "=r"(old_value), "=m"(*ptr) : "0"(increment), "m"(*ptr) : "cc", "memory"); return *ptr; } #include "ypipe.hpp" // 你自己的 ypipe_t 实现 ypipe_t<int, 10000> yqueue; /* 生产者线程函数(单元素 flush) */ void *yqueue_producer_thread(void *argv) { for (int i = 0; i < s_queue_item_num; ++i) { int unique = lxx_atomic_add(&s_count_push, 1); yqueue.write(unique, false); // 写入元素 yqueue.flush(); // 立即刷新,对消费者可见 } return nullptr; } /* 消费者线程函数 */ void *yqueue_consumer_thread(void *argv) { int last_value = 0; while (true) { int value; if (yqueue.read(&value)) // 非阻塞读取 { last_value = value; lxx_atomic_add(&s_count_pop, 1); } else { usleep(100); // 空队列时小睡 } if (s_count_pop >= s_queue_item_num * s_producer_thread_num) break; // 全部消费完退出 } return nullptr; } int main() { s_queue_item_num = 2000000; s_producer_thread_num = 1; s_consumer_thread_num = 1; s_count_push = 0; s_count_pop = 0; pthread_t prod, cons; pthread_create(&prod, nullptr, yqueue_producer_thread, nullptr); pthread_create(&cons, nullptr, yqueue_consumer_thread, nullptr); pthread_join(prod, nullptr); pthread_join(cons, nullptr); return 0; } 这里的 last_value = value;是什么意思?
11-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值