- std::hexfloat - 25 Updates
Bonita Montero <Bonita.Montero@gmail.com>: May 21 07:45AM +0200 > That's the wrong way around. What is a lot more frightening is > incompetent programmers writing code which depends on pointer casts > without realising that their code (a) gives undefined behaviour, ... ... in theory. |
"Fred.Zwarts" <F.Zwarts@KVI.nl>: May 21 09:25AM +0200 "Chris Vine" schreef in bericht news:20190520174928.c77ab71214afbd6375a5bbce@cvine--nospam--.freeserve.co.uk... >it will be optimized out where a cast would (but for strict aliasing) >work, and will still work where casting wouldn't (such as when casting >would result in misalignment). I need type punning often when accessing device registers. E.g., for a given VME address it makes a difference whether it is accessed in D8 mode (byte access), D16 (16-bit) mode or D32 (32-bit) mode. memcpy for device registers is a bad idea, because it is not defined what bit-size will be used for the copy. I have the feeling that only type punning, in combination with a volatile declaration is a good method for this purpose. But I am not sure that it is well defined in the C++ standard and that it is portable across different platforms. Fortunately, it works on the platforms that I use. Or is there a method that is clearly supported by the C++ standard? |
"Öö Tiib" <ootiib@hot.ee>: May 21 01:16AM -0700 On Monday, 20 May 2019 20:26:32 UTC+3, Ben Bacarisse wrote: > >> >>>> C++ does not. > I've cut the quoted text because your reply appears to be about the > above, not what I wrote (though I may have misunderstood). If something (on current case what exactly is the puzzle) was misunderstood then it was most likely me. I have some knowledge but bad communication skills. I know that C++ does not allow type punning through union. I have read it out from each version of standard and also from public communications and discussions of committee members. What compiler optimizations that is meant for is unclear but so it is. > > that placement new. > Thanks, yes, I read that part, but I could not find where a plain write > sets the active member. Sorry I take some online version of draft ... since I don't have the books under hand right now. There it is not plain write but assignment operator either built in or trivial. That http://eel.is/c++draft/class.union#5 of [class.union] http://eel.is/c++draft/class.union Otherwise, when there are no trivial ways to start lifetime of member (to make it active) then we have to use placement new. http://eel.is/c++draft/class.union#6 > It may be wrapped up in other more general text > about assignment, placement new, or some such. Indeed. They always try to keep one thing said only in one place and so it is painful to find sometimes where the one place is. > > Reading from not active member > > is undefined ... > Do you know where this is stated? In [basic.life] http://eel.is/c++draft/basic.life#7 Special guarantee that overrules [basic.life] is about common initial sequence: http://eel.is/c++draft/class.union#1 |
blt_14rUt9Szv@2mu00w.co.uk: May 21 08:26AM On Mon, 20 May 2019 19:04:59 +0100 >> 1.234000 >> Sorry, what was that you were saying? >Sorry, what exactly do you think you were proving? Are you having a slow brain day or something? You said it wouldn't work on new compilers with optimisation. I just proved it did just as I've said it works on every compiler I've ever tried it on. >Your crap code with undefined behaviour looks as if it is too >inconsequential for g++ to optimize against it. gcc/g++ will however LOL, oh please, give it up before you make a complete fool of yourself :) >warn that your code is non fit for purpose - it tells you that it >breaks strict aliasing rules. Clang doesn't and clang is a better compiler all round IMO. |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: May 21 11:49AM +0100 On Tue, 21 May 2019 08:26:45 +0000 (UTC) > Are you having a slow brain day or something? You said it wouldn't work on > new compilers with optimisation. I just proved it did just as I've said it > works on every compiler I've ever tried it on. The "it" which doesn't work is type punning through casting pointers. Your silly toy code with undefined behaviour proves absolutely nothing. An example has already been given up-thread of the differences which can arise in code emitted, depending on whether the -fno-strict-aliasing switch is applied or not. > >Your crap code with undefined behaviour looks as if it is too > >inconsequential for g++ to optimize against it. gcc/g++ will however > LOL, oh please, give it up before you make a complete fool of yourself :) I think you are the only one doing that. > >warn that your code is non fit for purpose - it tells you that it > >breaks strict aliasing rules. > Clang doesn't and clang is a better compiler all round IMO. So the standard says clearly that it is undefined behaviour but you say "ignore that because although gcc warns that it breaks strict aliasing, clang doesn't". If that is your approach to programming then "crap code" seems like too mild a description. |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: May 21 11:47AM +0100 On Tue, 21 May 2019 09:25:19 +0200 > well defined in the C++ standard and that it is portable across different > platforms. Fortunately, it works on the platforms that I use. > Or is there a method that is clearly supported by the C++ standard? The problem with aliasing arising from type punning concerns dereferencing pointers which do not represent the "dynamic type" (in C the "effective type") of the object being pointed to. The compiler is entitled to assume that the object obtain by dereferencing, say, an int* is actually an int and not a float. Does your case fall foul of this? A memcpy() is just another form of assignment: it so happens that with a cast the dynamic type of the result of the cast remains the source type, but with memcpy() it becomes the type of the destination. However I can see that memcpy() might be problematic with device registers because I don't think it has any atomicity guarantees. I guess in that case using a union with volatile members might be the answer: it is probably supported by your compiler. I don't actually know what the standard says about unions with volatile members - when I get a chance I must look it up. Chris |
Bart <bc@freeuk.com>: May 21 12:35PM +0100 On 21/05/2019 11:49, Chris Vine wrote: > The "it" which doesn't work is type punning through casting pointers. > Your silly toy code with undefined behaviour proves absolutely > nothing. Undefined behaviour because the language says so. >> Clang doesn't and clang is a better compiler all round IMO. > So the standard says clearly that it is undefined behaviour Only because the language says so. but you say > "ignore that because although gcc warns that it breaks strict aliasing, > clang doesn't". If that is your approach to programming then "crap > code" seems like too mild a description. It's only 'crap code' because the language says so. My opinion is that such code can be valid, and it can be well-defined (within a range of machines that might be the only ones you're interested in). Or the behaviour might be specific to that a range of machines. But that's OK because we're low-level programmers and we know what we're doing, right? My approach is not to use C or C++, partly because all their silly rules on UB (which seem to only exist to enable extreme optimisations) make life harder. And to use alternative languages. But if those other languages can successfully run the same code on the same machines without UB, then what are C and C++ playing at? This is a recent bit of code I used (expressed here as C, and inside a struct definition): ... union { // anonymous union int32_t modelist[4]; int32_t mode; }; I want to be able to access (read or write) the first 4 bytes of that union interchangeably as either .mode or .modelist[0], including writing as .modelist[0] then reading immediately as .mode. Isn't that technically UB in C or C++? I don't know, but the important thing is that I don't need to care! As for type-punning, in the alternate language I use it is an official feature! int->float type-punning, in C-like syntax, might be written as (float@)a, where a is an int, and it can also work as (float@)(a+b). (The C/C++ idiom would be *(float*)&a which only works on lvalues.) I don't see it as being anything different from this: a: dd 0 # 32-bit location mov [a],eax # write 32-bit int movd xmm0,[a] # read as 32-bit float What does the code mean? Well if eax contained 0x3F800000, then it's writing the binary representation of the IEEE float32 value 1.0. And the [a] could be [esi] where esi contains a pointer that is interpreted as int32_t* then float32* on successive lines. All perfectly reasonable things that you might want to do. |
"Fred.Zwarts" <F.Zwarts@KVI.nl>: May 21 01:43PM +0200 "Chris Vine" schreef in bericht news:20190521114749.d756ce67e9563bfa8c3fd2da@cvine--nospam--.freeserve.co.uk... >entitled to assume that the object obtain by dereferencing, say, an >int* is actually an int and not a float. Does your case fall foul of >this? Device registers normally do not use floating point types. They usually contain integer values, or bit patterns. But even if they contain a floating point type, it may not match the format of the host system, so one has to separate the manitissa and the exponent and construct a floating point value from it. I always use pointers to uint8_t, uint16_t, uint32_t or uint64_t to access such registers. I don't think that will be a problem. |
Jorgen Grahn <grahn+nntp@snipabacken.se>: May 21 12:29PM On Mon, 2019-05-20, Chris Vine wrote: > The number of people who don't trouble themselves to understand the > strict aliasing rules of C and C++ is surprising. "Don't trouble themselves" is a good way of putting it, because it's not /hard/ to grasp. If you think of memory as a store for /typed/ objects, plus the extra accomodations for char* and unions, you have the rough picture. /Jorgen -- // Jorgen Grahn <grahn@ Oo o. . . \X/ snipabacken.se> O o . |
David Brown <david.brown@hesbynett.no>: May 21 03:36PM +0200 On 21/05/2019 13:35, Bart wrote: >> Your silly toy code with undefined behaviour proves absolutely >> nothing. > Undefined behaviour because the language says so. Yes, exactly. >>> Clang doesn't and clang is a better compiler all round IMO. >> So the standard says clearly that it is undefined behaviour > Only because the language says so. Yes. It's fine to say you don't like this behaviour, and think that it should be possible to access data via pointers of any kind. It's fine to say that you choose to use compilers that implement such semantics - whether it is always the case in the compiler you use, or whether you need a switch like "-fno-strict-aliasing" to get that behaviour. You would be far from alone here. What is /not/ fine is to say that because /you/ think type-based alias analysis should not be used for optimisation, it's okay to write code that messes around with accessing data via incompatible types. If someone wants to write C code, they should write C code. If you want to write code in a variant of C with additional semantics, then you need to take appropriate steps to ensure that you use it with tools that guarantee those semantics. (And by that, I mean a tool that documents the behaviour - not a tool that "worked on your test code".) There is nothing wrong with writing code that is restricted like this, but you should be clear about it. None of this is new. The C language standards have been clear since C90 that accessing data via incompatible types is undefined behaviour. gcc introduced optimisations on type-based alias analysis around 20 years ago, and they did so because that's what many top-range C and C++ compilers did. >> clang doesn't". If that is your approach to programming then "crap >> code" seems like too mild a description. > It's only 'crap code' because the language says so. Yes - but since the discussion is about C or C++, it is those languages and their standards that matter. The standards say accessing an "float" object through an "int*" pointer is undefined behaviour - therefore it is undefined behaviour in C and C++, and if you write such code, it is crap C or C++ code. Particular implementations are free to give a definition for it - code can be crap C++ code while being valid "MSVC" code or valid "gcc -fno-strict-aliasing" code. > interested in). Or the behaviour might be specific to that a range of > machines. But that's OK because we're low-level programmers and we know > what we're doing, right? If you use compilers that define the semantics of this kind of cross-type access, that's fine. If you are writing more general C or C++, or using other compilers, it is not fine. > My approach is not to use C or C++, partly because all their silly rules > on UB (which seem to only exist to enable extreme optimisations) make > life harder. I fail to see how type-based alias analysis makes anything harder. In general, optimisations mean that you can write code in a clearer, simpler and more maintainable manner and let the compiler handle the efficiency details. I mean, how often do you actually want to access float data via an int* pointer? In real code, it's very rare that this sort of thing crops up. Being rare, there is no problem using valid alternatives like unions or memcpy - both of which can be tightly optimised by a compiler. > And to use alternative languages. But if those other languages can > successfully run the same code on the same machines without UB, then > what are C and C++ playing at? As always, you miss the point of undefined behaviour. And as always, you grossly overstate its importance. In the great majority of cases, things that are undefined behaviour in C or C++ are things that would not turn up in correct, sensible code in the first place. > as .modelist[0] then reading immediately as .mode. > Isn't that technically UB in C or C++? I don't know, but the important > thing is that I don't need to care! That will be valid in C, but (AFAIUI) invalid in C++. However, as others have noted, practical C++ compilers will define this behaviour to have the same meaning as in C. (This is not type punning, since mode and modelist[0] have the same types.) > As for type-punning, in the alternate language I use it is an official > feature! int->float type-punning, in C-like syntax, might be written as > (float@)a, where a is an int, and it can also work as (float@)(a+b). And how often is this actually relevant in real code? Or is this just another one of your "features" whose only purpose is to let you pretend your language is "better" than C? Just because something is easy to specify and implement does not mean it is useful. |
David Brown <david.brown@hesbynett.no>: May 21 03:45PM +0200 On 21/05/2019 09:25, Fred.Zwarts wrote: >> work, and will still work where casting wouldn't (such as when casting >> would result in misalignment). > I need type punning often when accessing device registers. That is strange. I work with device registers all the time, and I rarely use type punning. The usual way to access hardware registers is via "volatile uint32_t *" or similar pointers, with the size you want to use. Often you use structs rather than individual pointers, but it boils down to the same kind of volatile accesses. "Type punning" is when you have told the compiler that object A is of type T, and you know want to access it while pretending it is type U. You are not doing that, as far as I can tell. > across different platforms. Fortunately, it works on the platforms that > I use. > Or is there a method that is clearly supported by the C++ standard? Exactly how "volatile" works, and in particular for accesses via an absolute address cast to a pointer-to-volatile, is not as clearly defined as it could be in C and C++. C17 clarifies it - maybe newer C++ standards inherit this improvement. But all compilers have implemented it in the same obvious manner. |
Juha Nieminen <nospam@thanks.invalid>: May 21 01:52PM > I didn't even know hexfloat existed. It seems a spectacularly useless > manipulator. What on earth is the point of it? If you save a floating point value in ascii usint the normal decimal representation, in many (perhaps even most) cases there's a high chance of losing accuracy when it's read back, for the simple reason that the base-10 representation cannot accurately represent every single base-2 floating point value. Base-16 representation, however, can. It exactly represents the original floating point value, to the last bit, and nothing is lost in the conversion to either direction. Its advantage is that it's agnostic to the actual floating point value binary representation in the hardware (eg. it doesn't assume that it's an IEEE floating point value of a given size). Thus exact floating point values can be transferred between computers that may use different native floating point formats. If you save the floating point bits as raw data, you'll at the very least run into the problem of endianess, and of course you'll be assuming that both the source and target architectures use the exact same internal floating point bit representation. |
Bart <bc@freeuk.com>: May 21 03:45PM +0100 On 21/05/2019 14:36, David Brown wrote: >> It's only 'crap code' because the language says so. > Yes - but since the discussion is about C or C++, it is those languages > and their standards that matter. But how can code that expresses exactly the same thing be fine in one language and not in another? The standards say accessing an "float" > crap C or C++ code. Particular implementations are free to give a > definition for it - code can be crap C++ code while being valid "MSVC" > code or valid "gcc -fno-strict-aliasing" code. And here apparently the same code can be also be fine in a particular dialect of C or C++, or even using a particular set of compiler options, but be 'crap' when someone changes the compiler or option. > pointer? In real code, it's very rare that this sort of thing crops up. > Being rare, there is no problem using valid alternatives like unions or > memcpy - both of which can be tightly optimised by a compiler. In my own codebase I seem to use cast-based type-punning 5-10 times per application, But union-based type-punning is used all the time. Cast-based type-punning is convenient when there is no struct or union involved. It is very frequent that I'm interested in interpreting the bytes of a float as some integer value, or vice versa. (Here's an example using that non-C language; this prints the underlying binary bits of 0.1 which is a 64-bit float: print int64@(0.1):"b" Output is: 11111110111001100110011001100110011001100110011001100110011010 No faffing about with unions or *(int64_t*)&x casts (which won't work on 0.1), which apparently have UB anyway, or memcpy.) > And how often is this actually relevant in real code? Or is this just > another one of your "features" whose only purpose is to let you pretend > your language is "better" than C? It's 'better' in that it allows this sort of obvious stuff that people want to write, while C doesn't. |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: May 21 04:04PM +0100 On Tue, 21 May 2019 12:35:02 +0100 > > clang doesn't". If that is your approach to programming then "crap > > code" seems like too mild a description. > It's only 'crap code' because the language says so. Indeed so. It's crap because the standard says it is crap. > interested in). Or the behaviour might be specific to that a range of > machines. But that's OK because we're low-level programmers and we know > what we're doing, right? It is apparent from this thread that at least one person does not know what he is doing. You are entitled to your _opinion_ if you are going to write your own language. I think Alf also shares your view, but recognises (sadly) that it's not how C and C++ in fact are. Our religious spammer, whilst initially being ignorant of the strict aliasing rules, has said he has the same view as you about type punning via pointers and now permits it in his language (at present mainly vapourware I think, but I may be wrong). Presumably he is only intending to have his language used on a platform where alignment is not an issue or will have all objects padded to the most generic alignment. But we have been talking in this thread about C and C++. The standards for those languages determine whether the code is valid C or C++, and the standards say it isn't. Also, your _opinion_ might be valid if you are writing your own compiler: you could decide to provide type punning extensions, as indeed gcc has via union's and/or its -fno-strict-aliasing switch. But then you cannot call the code valid C or C++. Instead it is code conforming to your opinion. > My approach is not to use C or C++, partly because all their silly rules > on UB (which seem to only exist to enable extreme optimisations) make > life harder. What makes you say that memcpy() is harder than making a reinterpret_cast? I don't see it. It seems to me that the main reason people don't do it correctly is not the difficulty but that they just can't be bothered to understand the rules. Incidentally the standards permit any object pointer to be cast to char* and unsigned char* and dereferenced, if you want byte level access to an object. > And the [a] could be [esi] where esi contains a pointer that is > interpreted as int32_t* then float32* on successive lines. > All perfectly reasonable things that you might want to do. Possibly, but that is your language. It's not C or C++. You are converting a discussion about whether particular code is valid C or C++ into one about what some new language of yours should permit. |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: May 21 04:28PM +0100 On Tue, 21 May 2019 15:45:48 +0100 > > and their standards that matter. > But how can code that expresses exactly the same thing be fine in one > language and not in another? What a weird thing to say. Code that expresses exactly the same thing can be fine in one language but not in another because the respective standards for those languages say so. In setting a standard, it is a matter of choice for the language designer involving trade-offs between amongst other things optimization opportunities, convenience to the programmer, language complexity and safety. |
Paavo Helde <myfirstname@osa.pri.ee>: May 21 07:20PM +0300 On 21.05.2019 15:29, Jorgen Grahn wrote: > not /hard/ to grasp. If you think of memory as a store for /typed/ > objects, plus the extra accomodations for char* and unions, you have > the rough picture. This page https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8 claims that the following is UB in C++. void *p = malloc(sizeof(float)); float *fp = p; *fp = 1.0f; and one should use placement new instead: new (p) float {1.0f} ; Now that I find hard to crasp. If this is true, how is it even possible to write e.g. a custom memory allocator? A low-level memory allocator typically does not know anything about float, how is it possible to convert a memory block pointer to float* which has to be returned from allocate() (and later e.g. from std::vector<float>::data())? Do I really need to perform a dummy placement new in the beginning of the memory block, to obtain a valid float* pointer? |
Bonita Montero <Bonita.Montero@gmail.com>: May 21 06:23PM +0200 > void *p = malloc(sizeof(float)); > float *fp = p; > *fp = 1.0f; LOL. |
Paavo Helde <myfirstname@osa.pri.ee>: May 21 07:50PM +0300 On 21.05.2019 16:52, Juha Nieminen wrote: > chance of losing accuracy when it's read back, for the simple > reason that the base-10 representation cannot accurately represent > every single base-2 floating point value. With enough digits, a decimal (as well as any other) representation can get arbitrarily close to any real value, so it can also get arbitrarily close the any value represented exactly in base-2. It is not needed to represent the base-2 value exactly, it is just enough to provide any base-10 value which is rounded to the correct base-2 value. Historically there were indeed some round-trip bugs when serializing floating-point values, but AFAIK these bugs got fixed in the C runtime libraries about 10-20 years ago or so. Plus there are libraries which ensure the minimum number of decimal digits for perfect round-trip. Maybe you wanted to say that ensuring a proper round-trip is trickier in base-10 than in base-16 and it may easily waste more bytes than strictly necessary? |
Bart <bc@freeuk.com>: May 21 06:24PM +0100 On 21/05/2019 16:28, Chris Vine wrote: > designer involving trade-offs between amongst other things optimization > opportunities, convenience to the programmer, language complexity and > safety. Yes, but in this case what someone may want to do could be completely reasonable in itself, and could be well-defined on the processors they know their program will run on. When you look at the reasons why C and also C++ have made certain things UB, you could well find they don't apply in your case. Now, I've frequently written code in one language, which works perfectly well as native code, but it hits UB if I auto-translate to C. There are actually more problems generating C source as a target, then generating ASM. (And actually, when I briefly tried to target C++, there were even more problems.) |
jameskuyper@alumni.caltech.edu: May 21 10:34AM -0700 On Saturday, May 18, 2019 at 6:14:42 AM UTC-4, ... > I didn't even know hexfloat existed. It seems a spectacularly useless > manipulator. What on earth is the point of it? The C++ standard defines the behavior of hexfloat in terms of the behavior of std::printf() with a "%a" or "%A" format specifier. The C++ standard does not provide a detailed description of the behavior of printf(), cross-referencing the C standard for that definition. The C standard defines that behavior in part by recommending that conversions between strings and floating point values performed by standard library functions should be the same as those described for floating point constants. The less important advantage described below therefore traces back to things explained only in the C standard's description of floating point constants. The main advantage of hexfloat described below is far less subtle. It's an inherent consequence of using hexadecimal rather than decimal notation. I'll use pi as an example to demonstrate the issue. The first 17 significant digits of pi are 3.1415926535897932 Like most decimal floating point constants, 3.1415926535897932 cannot be represented exactly (which is a truth distinct from the fact that pi itself also cannot be represented exactly). On the system I'm currently using, three consecutive floating point values that can be represented exactly are: 3.141592653589792671908753618481568992137908935546875 3.141592653589793115997963468544185161590576171875000 3.141592653589793560087173318606801331043243408203125 I've shown 52 significant digits for those numbers, which is the minimum needed to display those values exactly. 17 digits are sufficient, however, to uniquely identify the middle value as the closest representable value to the exact value of pi. However the C standard permits 3.1415926535897932 to have any one of those three values. That would be true even if the constant used all 52 significant digits of the middle representation shown above. The corresponding hexadecimal floating point constants for those same values are 0X1.921FB54442D17P+1 0X1.921FB54442D18P+1 0X1.921FB54442D19P+1 The main advantage of hexadecimal floating point is simply that it takes only 11 significant hexadecimal "digits" to represent those values exactly, compared with the 17 significant decimal digits needed to specify a value that's closer to the best value than to either of the others, and compared with the 52 digits needed to represent those values exactly. However, there's another less important advantage. If FLT_RADIX is a power of 2 (as it is on almost, but not quite, every implementation of C or C++ currently in use), then 0X1.921FB54442D18P+1 is only allowed to be represented by that exact value, the other two values are not permitted as they would be for a decimal floating point constant. Also, 0X1.921FB54442D187P+1 is required to be rounded correctly to 0X1.921FB54442D18P+1; it's not allowed to round to 0X1.921FB54442D19P+1. That would not be the case for decimal floating point constants. These issues are important only for values that have so many significant digits that they push the limits on what values can be represented exactly. This almost never comes up with numbers that describe real- world measured quantities unless you're using "float" for your calculations, in which case the right solution is to use "double". It's only in high precision scientific calculations or pure mathematics that you're likely to run into situations where hexfloat actually becomes important. Therefore, if you aren't doing that kind of work, hexfloat probably seems pointless to you. However, it can be of critical importance to people who are doing that kind of work. |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: May 21 08:01PM +0200 On 21.05.2019 18:20, Paavo Helde wrote: > *fp = 1.0f; > and one should use placement new instead: > new (p) float {1.0f} ; The initialization of `fp` wouldn't compile as C++, but let's assume a `static_cast` or `reinterpret_cast` there. I favor the latter since it communicates better to the reader, but I believe for reasons having to do with a shortcoming of C++03 Herb Sutter and Andrei Alexandrescu recommended used `static_cast` in their old coding guidelines book. > > Now that I find hard to crasp. If this is true, how is it even possible > to write e.g. a custom memory allocator? Magic is indeed performed in a `new` expression: it transforms a `void*` produced by an allocator function, to a typed pointer. Another place this magic occurs, is in the member functions of a `std::allocator`. At least in C++03. I'm not as up-to-date as I should be to participate in C++ discussions. Anyway, even placement `new` doesn't save one from UB when there is an object other than byte in that memory chunk, and one obtains a pointer to it of an unrelated pointee type. Wham bang, you're formally dead. On the other hand, when there is no object of type other than bytes, then `reinterpret_cast` is technically good and so is placement `new`. > std::vector<float>::data())? Do I really need to perform a dummy > placement new in the beginning of the memory block, to obtain a valid > float* pointer? Nah, just FUD. Cheers!, - Alf |
Paavo Helde <myfirstname@osa.pri.ee>: May 21 09:02PM +0300 On 21.05.2019 20:24, Bart wrote: > Now, I've frequently written code in one language, which works perfectly > well as native code, but it hits UB if I auto-translate to C. Seems like a bug in the auto-translator. > There are > actually more problems generating C source as a target, then generating > ASM. Which ASM? All the 72 architectures covered by gcc? > (And actually, when I briefly tried to target C++, there were even more > problems.) I bet. |
Bonita Montero <Bonita.Montero@gmail.com>: May 21 08:47PM +0200 > communicates better to the reader, but I believe for reasons having to > do with a shortcoming of C++03 Herb Sutter and Andrei Alexandrescu > recommended used `static_cast` in their old coding guidelines book. static_cast, reinterpret_cast or C-style-cast - pure syntatic sugar. |
Bart <bc@freeuk.com>: May 21 07:56PM +0100 On 21/05/2019 19:02, Paavo Helde wrote: >> Now, I've frequently written code in one language, which works perfectly >> well as native code, but it hits UB if I auto-translate to C. > Seems like a bug in the auto-translator. Just a mismatch of languages, even though C in this case superficially works the same way. C as an intermediate language, even though it is very frequently used for that purpose, leaves a lot to be desired. >> actually more problems generating C source as a target, then generating >> ASM. > Which ASM? All the 72 architectures covered by gcc? The ones I had in mind were x64 and ARM64. I think I decided it would be simpler to target those two than to try and generate C code which would always compile warning-free and UB-free. Are there are any others I'm likely to be able to program in consumer equipment? I think it is quite common for applications to only need to run on a small number of architectures, but not want to be inconvenienced by a language designed to work with every conceivable architecture, past, present and future, and which therefore have to designate as UB, behaviour which cannot be guaranteed to work across all of them. >> (And actually, when I briefly tried to target C++, there were even more >> problems.) > I bet. I haven't tried it for a while. If I try it know on a smallish 3200-line generated-C program, I get the following number of lines of errors and warnings: No options (just -c) Lots of -W options gcc 0 lines 2900 lines g++ 1150 lines 2850 lines Typical error from the 1150-line output is: jpeg.c:420:5: error: invalid conversion from 'int64 (*)(jpeg_stream*)' {aka 'long long int (*)(jpeg_stream*)'} to 'void*' [-fpermissive] (Actually, this line is nothing to do with the application, but this language generates some metadata which includes an array of pointers to all functions used in the program. Since every function would have its own pointer type which depends on its signature, what should be the array element type? As far as I'm concerned, any function pointer can be stored within the same space as a void* pointer on all targets I want this to run on. It should be a non-issue.) |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: May 21 08:04PM +0100 On Tue, 21 May 2019 19:20:46 +0300 > std::vector<float>::data())? Do I really need to perform a dummy > placement new in the beginning of the memory block, to obtain a valid > float* pointer? Looking at operator new() rather than malloc() (both of which do pretty much the same thing), the C++14 standard (§3.7.4.1/2) says "The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function)." So you can use the storage provided by operator new() to access an object constucted in that storage. The dynamic type of the allocated storage is in effect the type of the first object constructed in it (here, a float). So for trivial types (which do not need to execute a constructor and destructor) I don't think it is necessary to use placement new - I think you can just assign or memcpy() into the memory. That is a feature of trivial types. It would be a ridiculous interpretation of the standard that malloc() operates differently. Footnote 36 itself says: "The intent is to have operator new() implementable by calling std::malloc() or std::calloc(), so the rules are substantially the same. C++ differs from C in requiring a zero request to return a non-null pointer." The dynamic type of malloc()'ed memory must surely, as in the case of the effective type in C, arise upon first construction of an object in that memory, either by placement new (C++) or assignment or memcpy() (C, and C++ trivial types). Chris |
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment