精华内容
下载资源
问答
  • 获取电脑硬件信息:用户操作系统、IE浏览器版本号、内存、CPU、硬盘编号、网卡Mac地址、cpu编号、主板编号、IP地址
  • Android 获取设备唯一编号

    千次阅读 2017-08-16 10:44:42
    有时需要对用户设备进行标识,所以希望能够得到一个稳定可靠并且唯一的识别码。虽然Android系统中提供了这样设备识别码,但是由于android系统版本、厂商定制系统中的Bug等限制,稳定性和唯一性并不理想。而通过其他...

    有时需要对用户设备进行标识,所以希望能够得到一个稳定可靠并且唯一的识别码。虽然Android系统中提供了这样设备识别码,但是由于android系统版本、厂商定制系统中的Bug等限制,稳定性和唯一性并不理想。而通过其他硬件信息标识也因为系统版本、手机硬件等限制存在不同程度的问题。
    下面提供一种 相对保全的办法。

     /**
         * deviceID的组成为:渠道标志+识别符来源标志+hash后的终端识别符
         * 渠道标志为:
         * andriod(a_)
         * 识别符来源标志:
         * 1, wifi mac地址(wifi_);
         * 2, IMEI(imei_);
         * 3, 序列号(sn_);
         * 4, id:随机码。若前面的都取不到时,则随机生成一个随机码,需要缓存。
         *
         * @param context
         * @return
         */
        public static String getDeviceId(Context context) {
            StringBuilder deviceId = new StringBuilder();
            // 渠道标志  
            deviceId.append("a_");
            try {
                //wifi mac地址
                WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                WifiInfo info = wifi.getConnectionInfo();
                String wifiMac = info.getMacAddress();
                if (!TextUtils.isEmpty(wifiMac)) {
                    deviceId.append("wifi");
                    deviceId.append(wifiMac);
                    Log.d("getDeviceId : ", deviceId.toString());
                    return deviceId.toString();
                }
                //IMEI(imei)
                TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
                String imei = tm.getDeviceId();
                if (!TextUtils.isEmpty(imei)) {
                    deviceId.append("imei_");
                    deviceId.append(imei);
                    Log.d("getDeviceId : ", deviceId.toString());
                    return deviceId.toString();
                }
                //序列号(sn)  
                String sn = tm.getSimSerialNumber();
                if (!TextUtils.isEmpty(sn)) {
                    deviceId.append("sn_");
                    deviceId.append(sn);
                    Log.d("getDeviceId : ", deviceId.toString());
                    return deviceId.toString();
                }
                //如果上面都没有, 则生成一个id:随机码  
                String uuid = getUUID();
                if (!TextUtils.isEmpty(uuid)) {
                    deviceId.append("id_");
                    deviceId.append(uuid);
                    Log.d("getDeviceId : ", deviceId.toString());
                    return deviceId.toString();
                }
            } catch (Exception e) {
                e.printStackTrace();
                deviceId.append("id_").append(getUUID());
            }
            Log.d("getDeviceId : ", deviceId.toString());
            return deviceId.toString();
        }
    
        /**
         * 得到全局唯一UUID
         */
        public static String getUUID() {
            String uuid = (String) SharedPreferencesUtil.getParam("SYSTEM_INFO", "UUID", "");
            if (TextUtils.isEmpty(uuid)) {
                uuid = UUID.randomUUID().toString();
                SharedPreferencesUtil.setParam("SYSTEM_INFO", "UUID", uuid);
            }
            return uuid;
        }

    网上有大神尝试使用java反射来获取唯一标识。如下

    //Android.os.SystemProperties.get() 读取系统属性,的方法是系统隐藏,无法直接使用.使用Java反射:
        private String getAndroidOsSystemProperties(String key) {
            String ret;
            try {
                Method systemProperties_get = Class.forName("android.os.SystemProperties").getMethod("get", String.class);
                if ((ret = (String) systemProperties_get.invoke(null, key)) != null)
                    return ret;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
    
            return "";
        }
    
        String[] propertys = {"ro.boot.serialno", "ro.serialno"};
    
        public void getStringSN() {
            for (String key : propertys) {
                String v = getAndroidOsSystemProperties(key);
                Log.e("####", "ylWang get " + key + " : " + v);
            }
        }

    下面这段代码 可以直接copy到工具类中使用

     public static String getDeviceId(Context context) {
            String deviceId = (String) SharedPreferencesUtil.getParam(SP_NAME_SYSTEM_INFO, SP_KEY_DEVICE_ID, "");
            if (TextUtils.isEmpty(deviceId)) {
                deviceId = getMD5(getDeviceInfo(context));
                SharedPreferencesUtil.setParam(SP_NAME_SYSTEM_INFO, SP_KEY_DEVICE_ID, deviceId);
            }
            return deviceId;
        }
    
        /**
         * deviceID的组成为:渠道标志+识别符来源标志+hash后的终端识别符
         * 渠道标志为:
         * andriod(a_)
         * 识别符来源标志:
         * 1, wifi mac地址(wifi_);
         * 2, IMEI(imei_);
         * 3, 序列号(sn_);
         * 4, id:随机码。若前面的都取不到时,则随机生成一个随机码,需要缓存。
         *
         * @param context
         * @return
         */
        private static String getDeviceInfo(Context context) {
            StringBuilder deviceId = new StringBuilder();
            // 渠道标志
            deviceId.append("a_");
            try {
                //wifi mac地址
                WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                WifiInfo info = wifi.getConnectionInfo();
                String wifiMac = info.getMacAddress();
                if (!TextUtils.isEmpty(wifiMac)) {
                    deviceId.append("wifi");
                    deviceId.append(wifiMac);
                    return deviceId.toString();
                }
                //IMEI(imei)
                TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
                String imei = tm.getDeviceId();
                if (!TextUtils.isEmpty(imei)) {
                    deviceId.append("imei_");
                    deviceId.append(imei);
                    return deviceId.toString();
                }
                //序列号(sn)
                String sn = tm.getSimSerialNumber();
                if (!TextUtils.isEmpty(sn)) {
                    deviceId.append("sn_");
                    deviceId.append(sn);
                    return deviceId.toString();
                }
                //如果上面都没有, 则生成一个id:随机码
                String uuid = UUID.randomUUID().toString();
                if (!TextUtils.isEmpty(uuid)) {
                    deviceId.append("id_");
                    deviceId.append(uuid);
                    return deviceId.toString();
                }
            } catch (Exception e) {
                e.printStackTrace();
                deviceId.append("id_").append(UUID.randomUUID().toString());
            }
            return deviceId.toString();
        }
    
    
        /**
         * 获取String的MD5值
         *
         * @param info 字符串
         * @return 该字符串的MD5值 返回值为十六进制的32位长度的字符串
         */
        private static String getMD5(String info) {
            try {
                //获取 MessageDigest 对象,参数为 MD5 字符串,表示这是一个 MD5 算法(其他还有 SHA1 算法等):
                MessageDigest md5 = MessageDigest.getInstance("MD5");
                //update(byte[])方法,输入原数据
                //类似StringBuilder对象的append()方法,追加模式,属于一个累计更改的过程
                md5.update(info.getBytes("UTF-8"));
                //digest()被调用后,MessageDigest对象就被重置,即不能连续再次调用该方法计算原数据的MD5值。可以手动调用reset()方法重置输入源。
                //digest()返回值16位长度的哈希值,由byte[]承接
                byte[] md5Array = md5.digest();
                //byte[]通常我们会转化为十六进制的32位长度的字符串来使用,本文会介绍三种常用的转换方法
                return new BigInteger(1, md5Array).toString(16);
            } catch (NoSuchAlgorithmException e) {
                return "";
            } catch (UnsupportedEncodingException e) {
                return "";
            }
        }

    参考文章:http://blog.csdn.net/aa1733519509/article/details/50053553

    展开全文
  • C和C++安全编码笔记:动态内存管理

    千次阅读 多人点赞 2020-05-04 18:23:21
    4.1 C内存管理: C标准内存管理函数: (1).malloc(size_t size):分配size个字节,并返回一个指向分配的内存的指针。分配的内存未被初始化为一个已知值。 (2).aligned_alloc(size_t alignment, size_t size):为...

    4.1 C内存管理:

    C标准内存管理函数:

    (1).malloc(size_t size):分配size个字节,并返回一个指向分配的内存的指针。分配的内存未被初始化为一个已知值。

    (2).aligned_alloc(size_t alignment, size_t size):为一个对象分配size个字节的空间,此对象的对齐方式是alignment指定的。alignment的值必须是实现支持的一种有效的对齐方式,size的值必须是alignment的整数倍,否则行为就是未定义的。此函数返回一个指向分配的空间的指针,如果分配失败,则返回一个空指针。

    (3).realloc(void* p, size_t size):将p所指向的内存块的大小改为size个字节。新大小和旧大小中较小的值那部分内存所包含的内容不变,新分配的内存未作初始化,因此将有不确定的值。如果内存请求不能被成功分配,那么旧的对象保持不变,而且没有值被改变。如果p是一个空指针,则该调用等价于malloc(size);如果size等于0,则该调用等价于free(p),但这种释放内存的用法应该避免。

    (4).calloc(size_t nmemb, size_t size):为数组分配内存,(该数组共有nmemb个元素,每个元素的大小为size个字节)并返回一个指向所分配的内存的指针。所分配的内存的内容全部被设置为0。

    内存分配函数返回一个指向分配的内存的指针,这块内存是按照任何对象类型恰当地对齐的,如果请求失败,则返回一个空指针。连续调用内存分配函数分配的存储空间的顺序和邻接是不确定的。所分配的对象的生存期从分配开始,到释放时结束。返回的指针指向所分配的空间的起始地址(最低字节地址)。

    free(void* p):释放由p指向的内存空间,这个p必须是先前通过调用aligned_alloc、malloc、calloc或realloc返回的。如果引用的内存不是被这些函数之一分配的或free(p)此前已经被调用过,将会导致未定义行为。如果p是一个空指针,则不执行任何操作

    由C内存分配函数分配的对象有分配存储期限。存储期限是一个对象的属性,它定义了包含该对象存储的最低潜在生存期。这些对象的生存期并不限于创建它的范围内,因此,如果在一个函数内调用malloc,那么在该函数返回后,已分配的内存仍然存在。

    对齐:完整的对象类型有对齐(alignment)要求,这种要求对可以分配该类型对象的地址施加限制。对齐是实现定义的整数值,它表示可以在一个连续的地址之间分配给指定的对象的字节数量。对象类型规定了每一个该类型对象的对齐要求。消除对齐要求,往往需要生成代码进行跨字边界域访问,或从更慢的奇数地址访问,从而减慢内存访问。

    完整的对象(complete object):对象中可以包含其它对象,被包含的对象称为子对象(subobject)。子对象可以是成员子对象、基类子对象,或数组元素。如果一个对象不是任何其它对象的子对象,那么它被称为一个完整的对象。

    对齐有一个从较弱的对齐到较强的对象(或更严格的对齐)的顺序。越严格的对齐,其对齐值越大。一个地址满足某一种对齐要求,也满足任何更弱的有效对齐要求。char、signed char、unsigned char类型的对齐要求最弱。对齐表示为size_t类型的值。每个有效对齐值都是2的一个非负整数幂。有效的对齐包括基本类型的对齐,加上额外的一组可选的实现定义的值。

    基本对齐(fundamental alignment)小于或等于在所有上下文中由编译器支持的最大对齐。max_align_t类型的对齐与在所有上下文中由编译器支持的对齐大小相同。扩展对齐(extended alignment)大于max_align_t类型的对齐。一个具有扩展对齐要求的类型也称为超对齐(overaligned)类型。每个超对齐类型,要么是一个结构或联合类型,其中一个成员已应用扩展对齐,要么它包含这样的类型。如果实现支持,可以使用aligned_alloc函数分配比正常更严格的对齐的内存。如果一个程序要求比alignof(max_align_t)更大的对齐,那么这个程序是不可移植的,因为对超对齐类型的支持是可选的。

    void test_aligned_alloc()
    {
    	const int arr_size = 11;
    	// 分配16字节对齐的数据
    #ifdef _MSC_VER
    	float* array = (float*)_aligned_malloc(16, arr_size * sizeof(float));
    #else
    	float* array = (float*)aligned_alloc(16, arr_size * sizeof(float));
    #endif
    	auto addr = std::addressof(array);
    	fprintf(stdout, "pointer addr: %p\n", addr);
    
    	fprintf(stdout, "char alignment: %d, float alignment: %d, max_align_t alignment: %d\n",
    		alignof(char), alignof(float), alignof(max_align_t));
    }

    在C标准中引入_Alignas关键字和aligned_alloc函数的主要理由是支持单指令多数据(SIMD)计算。

    4.2 常见的C内存管理错误:常见的与内存管理相关的编程缺陷包括:初始化错误、未检查返回值、对空指针或无效指针解引用、引用已释放的内存、对同一块内存释放多次、内存泄漏和零长度分配。

    初始化错误:由malloc函数返回的空间中的值是不确定的。一个常见的错误是不正确地假设malloc把分配的内存的所有位都初始化为零。

    // 读取未初始化的内存
    void test_memory_init_error()
    {
    	// 初始化大的内存块可能会降低性能并且不总是必要的.
    	// C标准委员会决定不需要malloc来初始化这个内存,而把这个决定留给程序员
    	int n = 5;
    	int* y = static_cast<int*>(malloc(n * sizeof(int)));
    	int A[] = {1, 2, 3, 4, 5};
    
    	for (int i = 0; i < n; ++i) {
    		y[i] += A[i];
    	}
    
    	std::for_each(y, y+n, [](int v) { fprintf(stdout, "value: %d\n", v); });
    	free(y);
    }

    不要假定内存分配函数初始化内存。不要引用未初始化的内存

    清除或覆写内存通常是通过调用C标准的memset函数来完成的。遗憾的是,如果不在写后访问内存,编译器优化可能会默默地删除对memset函数的调用。

    未检查返回值:内存分配函数的返回值表示分配失败或成功。如果请求的内存分配失败,那么aligned_alloc、calloc、malloc和realloc函数返回空指针

    // 检查malloc的返回值
    int* test_memory_return_value()
    {
    	// 如果不能分配请求的空间,那么C内存分配函数将返回一个空指针
    	int n = 5;
    	int* ptr = static_cast<int*>(malloc(sizeof(int) * n));
    	if (ptr != nullptr) {
    		memset(ptr, 0, sizeof(int) * n);
    	} else {
    		fprintf(stderr, "fail to malloc\n");
    		return nullptr;
    	}
    
    	return ptr;
    }

    Null或无效指针解引用:用一元操作符”*”解引用的指针的无效值包括:空指针、未按照指向的对象类型正确对齐的地址、生存期结束后的对象的地址。

    空指针的解引用通常会导致段错误,但并非总是如此。许多嵌入式系统有映射到地址0处的寄存器,因此覆写它们会产生不可预知的后果。在某些情况下,解引用空指针会导致任意代码的执行

    引用已释放内存:除非指向某块内存的指针已设置为NULL或以其它方式被覆写,否则就有可能访问已被释放的内存。

    // 引用已释放内存
    void test_memory_reference_free()
    {
    	int* x = static_cast<int*>(malloc(sizeof(int)));
    	*x = 100;
    	free(x);
    	// 从已被释放的内存读取是未定义的行为
    	fprintf(stderr, "x: %d\n", *x);
    	// 写入已经被释放的内存位置,也不大可能导致内存故障,但可能会导致一些严重的问题
    	*x = -100;
    	fprintf(stderr, "x: %d\n", *x);
    }

    从已被释放的内存读取是未定义的行为,但在没有内存故障时几乎总能成功,因为释放的内存是被内存管理器回收的。然而,并不保证内存的内容没有被篡改过。虽然free函数调用通常不会擦除内存,但内存管理器可能使用这个空间的一部分来管理释放或未分配的内存。如果内存块已被重新分配,那么其内容可能已经被全部替换。其结果是,这些错误可能检测不出来,因为内存中的内容可能会在测试过程中被保留,但在运行过程中被修改。

    写入已经被释放的内存位置,也不太可能导致内存故障,但可能会导致一些严重的问题。如果该块内存已被重新分配,程序员就可以覆写此内存,一个内存块是专门(dedicated)为一个特定的变量分配的,但在现实中,它是被共享(shared)的。在这种情况下,该变量中包含最后一次写入的任何数据。如果那块内存没有被重新分配,那么写入已释放的块可能会覆写并损坏内存管理器所使用的数据结构。

    多次释放内存:最常见的场景是两次释放(double-free)。这个错误是危险的,因为它会以一种不会立即显现的方式破坏内存管理器中的数据结构。

    // 多次释放内存
    void test_memory_multi_free()
    {
    	int* x = static_cast<int*>(malloc(sizeof(int)));
    	free(x);
    	// 多次释放相同的内存会导致可以利用的漏洞
    	free(x);
    }

    内存泄漏:当动态分配的内存不再需要后却没有被释放时,就会发生内存泄漏。

    零长度分配:C标准规定:如果所要求的空间大小是零,其行为是实现定义的:要么返回一个空指针,要么除了不得使用返回的指针来访问对象以外,行为与大小仿佛是某个非零值。

    // 零长度分配:不要执行零长度分配
    void test_memory_0_byte_malloc()
    {
    	char* p1 = static_cast<char*>(malloc(0));
    	fprintf(stderr, "p1 pointer: %p\n", std::addressof(p1)); // 是不确定的
    	free(p1);
    
    	p1 = nullptr;
    	char* p2 = static_cast<char*>(realloc(p1, 0));
    	fprintf(stderr, "p2 pointer: %p\n", std::addressof(p2)); // 是不确定的
    	free(p2);
    	
    	int nsize = 10;
    	char* p3 = static_cast<char*>(malloc(nsize));
    	char* p4 = nullptr;
    	// 永远不要分配0个字节
    	if ((nsize == 0) || (p4 = static_cast<char*>(realloc(p3, nsize))) == nullptr) {
    		free(p3);
    		p3 = nullptr;
    		return;
    	}
    
    	p3 = p4;
    	free(p3);
    }

    此外,要求分配0字节时,成功调用内存分配函数分配的存储量是不确定的。在内存分配函数返回一个非空指针的情况下,读取或写入分配的内存区域将导致未定义的行为。通常情况下,指针指向一个完全由控制结构组成的零长度的内存块。覆写这些控制结构损害内存所使用的数据结构。

    realloc函数将释放旧对象,并返回一个指针,它指向一个具有指定大小的新对象。然而,如果不能为新对象分配内存,那么它就不释放旧对象,而且旧对象的值是不变的。正如malloc(0),realloc(p, 0)的行为是实现定义的。

    不要执行零长度分配

    4.3 C++的动态内存管理:在C++中,使用new表达式分配内存并使用delete表达式释放内存。C++的new表达式分配足够的内存来保存所请求类型的对象,并可以初始化所分配的内存中的对象。

    new表达式是构造一个对象的唯一方法,因为不可能显示地调用构造函数。分配的对象类型必须是一个完整的对象类型,并且不可以(例如)是一个抽象类类型或一个抽象类数组。对于非数组对象,new表达式返回一个指向所创建的对象的指针;对于数组,它返回一个指向数组初始元素的指针。new表达式分配的对象有动态存储期限(dynamic storage duration)。存储期限定义了该对象包含的存储的生存期。使用动态存储的对象的生存期,不局限于创建该对象所在的范围。

    如果提供了初始化参数(即类的构造函数的参数,或原始整数类型的合法值),那么由new操作符所分配的内存被初始化。只有一个空的new-initializer()存在时,”普通的旧数据”(POD)类型的对象是new默认初始化(清零)的。这包括所有的内置类型。

    void test_memory_new_init()
    {
    	// 包括所有的内置类型
    	int* i1 = new int(); // 已初始化
    	int* i2 = new int; // 未初始化
    	fprintf(stdout, "i1: %d, i2: %d\n", *i1, *i2);
    
    	// 就地new没有实际分配内存,所以该内存不应该被释放
    	int* i3 = new (i1) int;
    	fprintf(stdout, "i3: %d\n", *i3);
    
    	delete i1;
    	delete i2;
    
    	// 通常情况下,分配函数无法分配存储时抛出一个异常表示失败
    	int* p1 = nullptr;
    	try {
    		p1 = new int;
    	} catch (std::bad_alloc) {
    		fprintf(stderr, "fail to new\n");
    		return;
    	}
    	delete p1;
    
    	// 用std::nothrow参数调用new,当分配失败时,分配函数不会抛出一个异常,它将返回一个空指针
    	int* p2 = new(std::nothrow) int;
    	if (p2 == nullptr) {
    		fprintf(stderr, "fail to new\n");
    		return;
    	}
    	delete p2;
    }

    就地new(placement new)是另一种形式的new表达式,它允许一个对象在任意内存位置构建。就地new需要在指定的位置有足够的内存可用。因为就地new没有实际分配内存,所以该内存不应该被释放。

    分配函数:必须是一个类的成员函数或全局函数,不能在全局范围以外的命名空间范围中声明,并且不能把它在全局范围内声明为静态的。分配函数的返回类型是void*。

    分配函数试图分配所请求的存储量。如果分配成功,则它返回存储块的起始地址,该块的长度(以字节为单位)至少为所要求的大小。分配函数返回的分配的存储空间内容没有任何限制。连续调用分配函数分配的存储的顺序、连续性、初始值都是不确定的。返回的指针是适当地对齐的,以便它可以被转换为任何具有基本对齐要求的完整对象类型的指针,并在之后用于访问所分配的存储中的对象或数组(直到调用一个相应的释放函数显示释放这块存储)。即使所请求的空间大小为零,请求也可能会失败。如果请求成功,那么返回值是一个非空指针值。如果一个指针是大小为零的请求返回的,那么对它解引用的效果是未定义的。C++在发起一个为零的请求时行为与C不同,它返回一个非空指针

    通常情况下,分配函数无法分配存储时抛出一个异常表示失败,这个异常将匹配类型为std::bad_alloc的异常处理器。

    如果用std::nothrow参数调用new,当分配失败时,分配函数不会抛出一个异常。相反,它将返回一个空指针

    当一个异常被抛出时,运行时机制首先在当前范围内搜索合适的处理器。如果当前范围内没有这样的处理程序存在,那么控制权将由当前范围转移到调用链中的一个更高的块。这个过程一直持续,直到找到一个合适的处理程序为止。如果在任何级别中都没有处理程序捕获该异常,那么std::terminate函数被自动调用。默认情况下,terminate调用标准C库函数abort,它会突然退出程序。当abort被调用时,没有正常的程序终止函数调用发生,这意味着全局和静态对象的析构函数不执行。

    在C++中,处理分配和分配失败的标准惯用法是资源获取初始化(Resource Acquisition Is Initializatiton, RAII)。RAII运用C++的对象生存期概念控制程序资源,如内存、文件句柄、网络连接、审计跟踪等。要保持对资源的跟踪,只要创建一个对象,并把资源的生存期关联到对象的生存期即可。这使你可以使用C++对象管理设施来管理资源。其最简单的形式是,创建一个对象,它在构造函数获得资源,并在其析构函数中释放资源。

    class intHandle {
    public:
    	explicit intHandle(int* anInt) : i_(anInt) {} // 获取资源
    	~intHandle() { delete i_; } // 释放资源
    
    	intHandle& operator=(const int i)
    	{
    		*i_ = i;
    		return *this;
    	}
    
    	int* get() { return i_; } // 访问资源
    
    private:
    	intHandle(const intHandle&) = delete;
    	intHandle& operator=(const intHandle&) = delete;
    	int* i_;
    };
    
    // 资源获取初始化(Resource Acquisition Is Initialization, RAII)
    void test_memory_arii()
    {
    	intHandle ih(new int);
    	ih = 5;
    	fprintf(stdout, "value: %d\n", *ih.get());
    
    	// 使用std::unique_ptr能完成同样的事情,而且更简单
    	std::unique_ptr<int> ip(new int);
    	*ip = 5;
    	fprintf(stdout, "value: %d\n", *ip.get());
    }

    如果发生下列任何情况,new表达式抛出std::bad_array_new_length异常,以报告无效的数组长度:(1).数组的长度为负;(2).新数组的总大小超过实现定义的最大值;(3).在一个大括号初始化列表中的初始值设定子句数量超过要初始化的元素数量(即声明的数组的大小)。只有数组的第一个维度可能会产生这个异常,除第一个维度外的维度都是常量表达式,它们在编译时检查。

    // 抛出std::bad_array_new_length的三种情况
    void test_memory_bad_array_new_length()
    {
    	try {
    		int negative = -1;
    		new int[negative]; // 大小为负
    	} catch(const std::bad_array_new_length& e) {
    		fprintf(stderr, "1: %s\n", e.what());
    	}
    
    	try {
    		int small = 1;
    		new int[small]{1, 2, 3}; // 过多的初始化值设定
    	} catch(const std::bad_array_new_length& e) {
    		fprintf(stderr, "2: %s\n", e.what());
    	}
    
    	try {
    		int large = INT_MAX;
    		new int[large][1000000]; // 过大
    	} catch(const std::bad_alloc& e) {
    		fprintf(stderr, "3: %s\n", e.what());
    	}
    }

    释放函数:是类的成员函数或全局函数,在一个全局范围以外的命名空间范围声明释放函数或全局范围内声明静态的释放函数都是不正确的。每个释放函数都返回void,并且它的第一个参数是void*。对于这些函数的两个参数形式,第一个参数是一个指向需要释放的内存块的指针,第二个参数是要释放的字节数。这种形式可能被用于从基类中删除一个派生类对象。提供给一个释放函数的第一个参数的值可以是一个空指针值,如果是这样的话,并且如果释放函数是标准库提供的,那么该调用没有任何作用

    如果提供给标准库中的一个释放函数的参数是一个指针,且它不是空指针值,那么释放函数释放该指针所引用的存储,引用指向已释放存储(deallocated storage)的任何部分的所有指针无效。使用无效的指针值(包括将它传递给一个释放函数)产生的影响是未定义的。

    垃圾回收:在C++中,垃圾回收(自动回收不再被引用的内存区域)是可选的,也就是说,一个垃圾回收器(Garbage Collector, GC)不是必须的。一个垃圾回收器必须能够识别动态分配的对象的指针,以便它可以确定哪些对象可达(reachable),不应该被回收,哪些对象不可达(unreachable),并可以回收。

    4.4 常见的C++内存管理错误:常见的与内存管理相关的编程缺陷,包括未能正确处理分配失败、解引用空指针、写入已经释放的内存、对相同的内存释放多次、不当配对的内存管理函数、未区分标量和数组,以及分配函数使用不当。

    未能正确检查分配失败:new表达式要么成功要么抛出一个异常。new操作符的nothrow形式在失败时返回一个空指针,而不是抛出一个异常。

    // 未能正确检查分配失败
    void test_memory_new_wrong_usage()
    {
    	// new表达式,要么成功,要么抛出一个异常
    	// 意味着,if条件永远为真,而else子句永远不会被执行
    	int* ip = new int;
    	if (ip) { // 条件总是为真
    		
    	} else {
    		// 将永远不执行
    	}
    	delete ip;
    
    	// new操作符的nothrow形式在失败时返回一个空指针,而不是抛出一个异常
    	int* p2 = new(std::nothrow)int;
    	if (p2) {
    		delete p2;
    	} else {
    		fprintf(stderr, "fail to new\n");
    	}
    }

    不正确配对的内存管理函数:使用new和delete而不是原始的内存分配和释放。C内存释放函数std::free不应该被用于由C++内存分配函数分配的资源,C++内存释放操作符和函数也不应该被用于由C内存分配函数分配的资源。C++的内存分配和释放函数分配和释放内存的方式可能不同于C内存分配和释放函数分配和释放内存的方式。因此,在同一资源上混合调用C++内存分配和释放函数及C内存分配和释放函数是未定义的行为,并可能会产生灾难性的后果。

    class Widget {};
    
    // 不正确配对的内存管理函数
    void test_memory_new_delete_unpaired()
    {
    	int* ip = new int(12);
    	free(ip); // 错误,应使用delete ip
    
    	int* ip2 = static_cast<int*>(malloc(sizeof(int)));
    	*ip2 = 12;
    	delete ip2; // 错误,应使用free(ip2)
    
    	// new和delete操作符用于分配和释放单个对象
    	Widget* w = new Widget();
    	delete w;
    
    	// new[]和delete[]操作符用于分配和释放数组
    	Widget* w2 = new Widget[10];
    	delete [] w2;
    
    	// operator new()分配原始内存,但不调用构造函数
    	std::string* sp = static_cast<std::string*>(operator new(sizeof(std::string)));
    	//delete sp; // 错误
    	operator delete (sp); // 正确
    }

    在C++的new表达式分配的对象上调用free,因为free不会调用对象的析构函数。这样的调用可能会导致内存泄漏、不释放锁或其它问题,因为析构函数负责释放对象所使用的资源。

    new和delete操作符用于分配和释放单个对象;new[]和delete[]操作符用于分配和释放数组。

    当分配单个对象时,先调用operator new()函数来分配对象的存储空间,然后调用其构造函数来初始化它。当一个对象被删除时,首先调用它的析构函数,然后调用相应的operator delete()函数来释放该对象所占用的内存。当分配一个对象数组时,先调用operator new[]()对整个数组分配存储空间,随后调用对象的构造函数来初始化数组中的每个元素。当删除一个对象数组时,首先调用数组中的每个对象的析构函数,然后调用operator delete[]()释放整个数组所占用的内存。要将operator delete()与operator new()一起使用,并将operator delete[]()与operator new[]()一起使用。如果试图使用operator delete()删除整个数组,只有数组的第一个元素所占用的内存将被释放,一个明显的内存泄漏可能会导致被利用。

    new和operator new():可以直接调用operator new()分配原始内存,但不调用构造函数

    函数operator new()、operator new[]()、operator delete()和operator delete[]()都可以被定义为成员函数。它们是隐藏继承的或命名空间范围中同名函数的静态成员函数。与其它内存管理函数一样,重要的是让它们正确配对。如果对象所使用的内存不是通过调用operator new()获得的,同时对其使用operator delete(),就可能发生内存损坏。

    多次释放内存:智能指针是一个类类型(class type),它具有重载的”->”和”*”操作符以表现得像指针。比起原始指针,智能指针往往是一种更安全的选择,因为它们可以提供原始指针中不存在的增强行为,如垃圾回收、检查空,而且防止使用在特定情况下不合适或危险的原始指针操作(如指针算术和指针复制)。引用计数智能指针对它们所引用的对象的引用计数进行维护。当引用计数为零时,该对象就被销毁。

    释放函数抛出一个异常:如果释放函数通过抛出一个异常终止,那么该行为是未定义的。释放函数,包括全局的operator delete()函数,它的数组形式与其用户定义的重载,经常在销毁类类型的对象时被调用,其中包括作为某个异常结果的栈解开。允许栈解开期间抛出异常导致逃避了调用std::terminate,而导致std::abort函数调用的默认效果。这种情况可能被利用为一种拒绝服务攻击的机会。因此,释放函数必须避免抛出异常

    4.5 内存管理器:既管理已分配的内存,也管理已释放的内存。在大多数操作系统中,包括POSIX系统和Windows,内存管理器作为客户进程的一部分运行。分配给客户进程的内存,以及供内部使用而分配的内存,全部位于客户进程的可寻址内存空间内。操作系统通常提供内存管理器作为它的一部分(通常是libc的一部分)。在较不常见的情况下,编译器也可以提供替代的内存管理器。内存管理器可以被静态链接在可执行文件中,也可以在运行时确定。

    4.6 Doug Lea的内存分配器:GNU C库和大多数Linux版本(例如Red Hat、Debian)都是将Doug Lea的malloc实现(dlmalloc)作为malloc的默认原生版本。Doug Lea独立地发布了dlmalloc,其他一些人对其作了修改并用作GNU libc的分配器。

    动态分配的内存也可能遭遇缓冲区溢出。例如,缓冲区溢出可被用于破坏内存管理器所使用的数据结构从而能够执行任意的代码。

    解链(unlink)技术:最早由Solar Designer提出。unlink技术被用于利用缓冲区溢出来操纵内存块的边界标志,以欺骗unlink()宏向任意位置写入4字节数据。

    4.7 双重释放漏洞:Doug Lea的malloc还易于导致双重释放漏洞。这种类型的漏洞是由于对同一块内存释放两次造成的(在这两次释放之间没有对内存进行重新分配)。要成功地利用双重释放漏洞,有两个条件必须满足:被释放的内存块必须在内存中独立存在(也就是说,其相邻的内存块必须是已分配的,这样就不会发生合并操作了),并且该内存所被放入的筐(在dlmalloc中,空闲块被组织成环形双链表,或筐(bin))必须为空。

    写入已释放的内存:一个常见的安全缺陷。

    RtlHeap:并非只有使用dlmalloc开发的应用程序才可能存在基于堆的漏洞。使用微软RtlHeap开发的应用程序在内存管理API被误用时也有可能被利用。与大多数软件一样,RtlHeap也在不断地进化,不同的Windows版本通常都有不同的RtlHeap实现,它们的行为稍有不同。

    4.8 缓解策略:有很多缓解措施可以用来消除或减少基于堆的漏洞。

    空指针:一个明显的可以减少C和C++程序中漏洞数量的技术就是在指针所引用的内存被释放后,将此指针设置为NULL。空悬指针(执行已释放内存的指针)可能导致赋写已释放内存和双重释放漏洞。将指针置为NULL后,任何企图解引用该指针的操作都会导致致命的错误,这样就增加了在实现和测试过程中发现问题的几率。并且,如果指针被设置为NULL,内存可以被”释放”多次而不会导致不良后果

    一致的内存管理约定:(1).使用同样的模式分配和释放内存;(2).在同一个模块中,在同一个抽象层次中分配和释放内存;(3).让分配和释放配对。

    随机化:传统的malloc函数调用返回的内存分配地址在很大程度上是可预测的。通过让内存管理程序返回的内存块地址随机化,可以使对基于堆的漏洞利用变得更加困难。

    运行时分析工具:Valgrind、Purify、Insure++、Application Verifier。

    GitHubhttps://github.com/fengbingchun/Messy_Test

    展开全文
  • 这个周末挺冷的,宅在家,看完了吴军写的《硅谷之谜》,晚上忙完生活琐事之后,突然想起了最近工作时遇到了唯一编码生成的问题,... 实际工作中,我们也许会用到根据时间来生成一个有序的唯一编码作为项目编号或者流...

        这个周末挺冷的,宅在家,看完了吴军写的《硅谷之谜》,晚上忙完生活琐事之后,突然想起了最近工作时遇到了唯一编码生成的问题,突然有想法,就写了一个方法并通过了自己的测试,觉得还不错,所以分享一下。

           实际工作中,我们也许会用到根据时间来生成一个有序的唯一编码作为项目编号或者流水号之类的,例如前缀+yyyyMMddHHmmss+四位有序的数字(CEO201702262330320001).为了支持并发,我加了锁,然后如果同一秒内生成的数量超过了四位数,那么做了自动等待下一秒,继续生成编码。保证了即使是多线程也是唯一,且有序。具体代码如下,有需要的朋友看参考下,当然如果有不足的地方也欢迎指正。

           另外,目前这个算法是不支持分布式的,稍后,找个时间加入redis,来实现满足分布式的环境。

     

    代码如下:

     class GUniqueCode
        {
            private static readonly object lockTimeCode = new object();
            private static Dictionary<string, int> dic = new Dictionary<string, int>();

            public List<string> GSeqTimeCode(string preCode="",int pCount=1)
            {
                List<string> tmLs = new List<string>();
                     int secondCount = 1;
                     StringBuilder sb = new StringBuilder();

                for (int i = 0; i < pCount; i++)
                {
                    sb.Clear();
                    sb.Append(preCode);
                    string timeCode = DateTime.Now.ToString("yyyyMMddHHmmss");
                    sb.Append(timeCode);
              
                    lock (lockTimeCode)
                    {
                        if (!dic.ContainsKey(timeCode))
                        {
                            if (dic.Count > 10)//定期清除内存
                                dic.Clear();
                            secondCount = 1;
                            dic.Add(timeCode, 1);
                        }
                        else
                        {
                            if (dic[timeCode] >= 9999)//同个时间如果生成的序号数超过9999,将等待下一秒继续生成编号
                            {
                              while(timeCode ==DateTime.Now.ToString("yyyyMMddHHmmss"))
                              {
                                  Thread.Sleep(0);
                              }
                              continue;
                            }
                            dic[timeCode]++;
                            secondCount = dic[timeCode];
                        }

                        string strNo = secondCount.ToString().PadLeft(4, '0');
                        sb.Append(strNo);
                        tmLs.Add(sb.ToString());
                    }
                }

                return tmLs;
            }

    转载于:https://my.oschina.net/u/2954787/blog/846737

    展开全文
  • 内存和栈内存

    2019-07-01 14:00:28
    计算机中内存就像一栋存放数据的大楼,而要找到数据,就需要知道数据所在楼层的编号,这个编号就是物理地址,就像身份证号,通过身份证号可以找到唯一对应的人,通过物理地址可以找到唯一对应的内存块。 如果数据...

    计算机中内存就像一栋存放数据的大楼,而要找到数据,就需要知道数据所在楼层的编号,这个编号就是物理地址,就像身份证号,通过身份证号可以找到唯一对应的人,通过物理地址可以找到唯一对应的内存块。
    在这里插入图片描述
    如果数据杂乱无章地分布于内存中,那么在数据的操作、内存的管理等方面都会有各种各样的问题。所以内存进行了分区:
    1、栈区:用来存储局部变量等
    2、堆区:用来存储变量所指向的具体数据项等
    3、全局区:用来存储全局变量和静态变量
    4、文字常量区:用来存储常量字符串
    5、程序代码区:用来存储二进制代码
    在这里插入图片描述
    这里主要讲一下栈区和堆区的区别:
    既然是存储容器,就会有满溢的时候。
    栈:申请系统提供栈内存时,如果申请的空间小于栈内存剩余的空间,系统就会提供;然而申请的空间大于栈内存剩余空间时,系统将提示栈溢出异常。而且栈内存一般比较小。
    堆:申请系统提供堆内存时,系统会先查询一个记录空闲的堆内存地址链表,如果找到一个空间大于等于申请空间的堆内存,系统就会提供,并将多出的部分重新放入空闲地址链表。因为这个机制,加上可用的虚拟内存,堆内存会比较大。

    展开全文
  • 主流DDR内存芯片与编号识别

    千次阅读 2006-10-09 21:30:00
    发布日期:2004年4月8日 作者: 赵效民我们关注哪些厂商 本文授权《电脑爱好者》杂志单独采用,其他纸媒体谢绝转载 在本站前不久的内存评测与优化专题中,经常会提到一些内存芯片编号,最近在一些论坛中也发现了...
  • WINCE 获取智能设备唯一编号

    千次阅读 2012-04-27 13:51:01
    //提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法 namespace LogisticsManage { public partial class Form1...
  • 编码、改bug、提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分享了JVM及其启动流程,今天介绍一下JVM内部的一些区域,以及具体的区域在运行过程中会发生哪些异内存常! 其实也就对应了内存管理的第一篇中...
  • C语音 变量与内存

    千次阅读 2016-01-11 09:41:00
    在前面一节中简单介绍了变量的使用,当我们定义一个变量的时候,系统就会为变量分配一块存储空间。而变量的数值在内存中是以二进制的形式存储的,这讲来深入...内存的每一个字节都有一个唯一编号,这个编号就称为地址
  • Linux内存描述之内存页面page--Linux内存管理(四)

    万次阅读 多人点赞 2016-08-31 14:18:44
    日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 ... Linux内存管理 1 前景回顾1.1 UMA和NUMA两种模型共享存储型多处理机有两种模型 均匀存储器存取(Uniform-Memory-Access,简称UMA)模型 非均匀存储器
  • Linux内存管理

    千次阅读 2018-02-05 14:49:01
    在linux下,使用top,vmstat,free等命令查看系统或者进程的内存使用情况时,经常看到buff/cache memeory,swap,avail Mem等,他们都代表什么意思呢?这篇文章将来聊一聊Linux下的内存管理并解答这个问题。 讨论...
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 ... Linux内存管理 1 前景回顾前面我们讲到服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)1.1 UMA和NUMA两种模型共享存储型多处理机有两种模型
  • 内存

    千次阅读 2008-04-14 14:15:00
    内存1 内存发展1. 30pin SIMM 2. 30pin和72pin的 SIMM FPM(Fast Page Mode) 3. 72pin EDO(Extended Data Out) RAM 4. 168pin DIMM SD(Synchronous Dynamic) RAM (1)PC66 (2)PC100 (3)PC133 5. VCM
  • Linux内存描述之概述--Linux内存管理(一)

    万次阅读 多人点赞 2016-08-31 13:29:34
    日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 Linux-4.7 X86 & arm ... 链接 内存管理(一)内存模型之Node Linux 内存管理 重要结构体 Bootmem机制 Linux-2.6.32 NUMA架构之内存和调度
  • 虽然不是专业的CS出生,想想接触CS相关的东西也这么久了,居然不知道浮点数在内存中的表示是怎样的,实在是惭愧啊!于是最近查阅了相关资料,终于弄清了浮点数在内存中的表示,现在对相关的东西做一个总结。  以下...
  • * - 序列节点(SEQUENTIAL ):多个线程创建同一个顺序节点时候,每个线程会得到一个带有编号的节点,节点编号是递增不重复的 */ /*** * 使用了zk节点唯一性来分布式保证高并发锁 * 缺点:如果使用的临时节点,...
  • [内存管理]连续内存分配器(CMA)概述

    千次阅读 2013-10-21 09:00:06
    连续内存分配器(CMA - Contiguous Memory Allocator)是一个框架,允许建立一个平台无关的配置,用于连续内存的管理。然后,设备所需内存都根据该配置进行分配。 这个框架的主要作用不是分配内存,而是解析和管理...
  • Java 虚拟机内存分配机制

    千次阅读 2017-09-15 15:34:45
    对于大多数的程序员来说,Java 内存比较流行的说法便是堆和栈,这其实是非常粗略的一种划分,这种划分的“堆”对应内存模型的 Java 堆,“栈”是指虚拟机栈,然而 Java 内存模型远比这更复杂,想深入了解 Java 的...
  • Android最佳性能实践(二)——分析内存的使用情况

    万次阅读 多人点赞 2015-02-12 09:09:52
    所以,唯一能够解决问题的办法,就是尝试去分析应用程序的内存使用情况,那么本篇文章就会教大家如何进行分析。虽说现在的手机内存都已经非常大了,但是我们大家都知道,系统是不可能将所有的内存都分配给我们的应用...
  • 程序内存四区

    千次阅读 2018-08-09 19:38:37
    代码在内存中进行执行的时候,我们粗略的将程序所占用的内存分为四个区域—-栈区,堆区,全局区,代码区,每个程序都有唯一的四个内存区域.我们需要熟悉和了解各个区域的特性,例如存储什么类型的数据, 有谁去申请开辟, 又...
  • 内存的理解

    2016-04-13 09:52:52
    内存,软件运行与内存的关系
  • 2、数据在内存中的编码 接下来看下不同数据类型在内存中的表示形式 对于整数,直接用补码表示,如 char -4=0b1111 1100=0xFC int -4=0xFFFFFFFC 对于浮点型数据,不同标准的编码表示也不一样,最常用的是IEEE754...
  • 【Linux】Linux的虚拟内存详解(MMU、页表结构)

    万次阅读 多人点赞 2018-07-16 20:16:28
    内存是程序得以运行的重要物质基础。如何在有限的内存空间运行较大的应用程序,曾是困扰人们的一个难题。为解决这个问题,人们设计了许多的方案,其中最成功的当属虚拟内存技术。Linux作为一个以通用为目的的现代...
  • 内存单元和内存单元地址

    千次阅读 2013-10-28 15:01:19
    内存单元地址  我们每个人,在不断的成长,无论何时,都在不断的学习着,通过书本,通过师傅,通过失败,我们不断的总结着,不断的整理着自己的知识,使其系统化,理论化,以便能解决更多复杂的问题。但是具有这样...
  • 内存的概念

    2019-03-14 17:10:26
    内存 在我们明白Python怎么存储数据之前,我们需要先引入内存的概念: 内存的基本就够是线性排列的一批存储单元,每个单元...内存单元具有唯一编号,成为单元地址,简称为地址,单元地址从开始连续排列 ,如下雨...
  • 内存优化

    千次阅读 2016-09-23 21:22:56
    方便显示内存使用和GC情况; 快速定位卡顿是否和GC有关; 快速定位Crash是否和内存占用过高有关; 快速定位潜在的内存泄漏问题; 简单易用,但不能准确定位问题; Allocation Tracker: 定位代码中分配的对象的...
  • Redis虚拟内存 一、虚拟内存简介: 和大多NoSQL数据库一样,Redis同样遵循了Key/Value数据存储模型。在有些情况下,Redis会将Keys/Values保存在内存中以提高数据查询和数据修改的效率,然而这样的做法并非总是很好...
  • 1.内存泄漏的定义    一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc...
  • 内存带宽

    千次阅读 2014-06-13 18:10:56
    显然,内存的容量决定“仓库”的大小,而内存的带宽决定“桥梁”的宽窄,两者缺一不可,这也就是常常说道的“内存容量”与“内存速度”。除了内存容量与内存速度,延时周期也是决定其性能的关键。当CPU需要内
  • 什么是内存地址

    2018-08-21 07:41:00
    什么是内存地址? 内存地址是一个唯一的代表某个存有数据的内存单元的数值编码。 转载于:https://www.cnblogs.com/q2546/p/9509197.html

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 188,258
精华内容 75,303
关键字:

内存唯一编号