C Language Conundrum

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
In response to the original post; you're expecting the language to do too much for you. C is a lower level language, and memset() is a very old C function. What you get is exactly what you see.

void *memset(void *s, int c, size_t n);

It sets "n" bytes of memory, starting at the address specified by "s" to the value specified by "c", and returns the value of s. That's all it does, no more no less. It works exactly as the man page describes.
Hi,

Well i would hope the documentation is correct :)

You realize what you said is true but that it does not strictly speaking require an "int" in the argument "int c". That is because memset can not set "ints" it can only set bytes (or as we take them to be sometimes which is chars or uchars).

THAT is what is causing the confusion i am seeing on the web in various places. People see the prototype and then misinterpret the int to mean you can set int's when you cant.

As i said before, if we HAD to use an 'int' in every function definition then we would never be able to pass a char such as:
void MyFunc(char c)

we would be forced to do:
void MyFunc(int c).

The reason the form void MyFunc(char c) is valid is because first and foremost we are allowed to do that, and second because any extra stack time does not add significantly to most function calls because the body takes much more time than the call itself. This is well known, and when it does not apply the function is used inline.

But im still open to other arguments.
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
What "standard" are you talking about? And what is the "going from one standard to another" babble about?

When you compile a C program, the only way that a pointer can be dereferenced is if the compiler knows what type of data it is pointing to. If it is a void pointer then the type of data it is pointing to is unknown. Where are you getting this silly notion that anyone is claiming that a void pointer doesn't point to anything. It's value is that of a pointer on the underlying hardware. The compiler keeps track of the type of data that a pointer points to. If it is a void pointer, then the compiler simply does not know what type of data it points to.

You can keep insisting on your fantasy interpretation. I can't stop you. But don't be surprised when you keep being "misled" by clearly defined and well-documented functions because you insist on viewing them through that fantasy world's lens.
Hi there,

I'm sorry but i cant stop myself from thinking about the old adage, "We mock what we dont understand".
I say that because you keep using some mocking words like "babble" and "fantasy interpretation" but you dont seem to understand that someone that has been there and done that is going to read your statements and see right away that your interpretation is coming from a limited viewpoint where you have not ever done any of this before and so your only response can be one of surprise. However, i would think you would be more open minded than that and at least stay open to the *possibility* that something other than what you think is the whole picture might be in effect.

The first point you already agreed to now, and that is that the pointer must point to something. That's enough of an argument to close this case by itself although i wont stop there. But the first question then is, what does it point to?
Your answer:
It points to an unknown type.
My answer:
It either points to an unknown type (from the standpoint of some C compilers) or to a sequence of bytes.

Your answer is very good i must say because it covers most of what we see INSIDE a C program. However, the binary standard is that it points to a sequence of bytes, and that can be proved by virtue of knowing the lowest level storage chunk in the memory space, and that is the byte. It's the compiler that sees it as something else once it is cast, but in the mean time it points to bytes because that's the only thing it possibly can point to, without a cast. We can state that it is just "unknown" but if we call a function in the resident C program from outside the C program we get a pointer to bytes, period. This is evident from the fact that we can read the bytes with this outside program that may be using a different standard for memory like "peek". We can make it a pointer to something else like ints, but before we do that it already points to the start of that memory block and it must or else we would not be able to cast it because there would be nothing to cast.

If this does not meet with your expectations that's ok, but i just thought i would point'er that out (ha ha).

If you want to call this a 'cast' that's up to you:


unsigned long MyFunc(void)
{
return (unsigned long)p;
}

When this is inside a DLL and called from outside the resident program (using the C calling convention the returned value is a pointer, and the bytes can be read with "peek(address)".

If you want to state that p is cast to ulong so that means it's no longer a void pointer, that's up to you. The way i look at it is that p must have been pointing to something already and we are just getting that address, nothing more.

We could probably do this too:
void* MyFunc(void)
{
return p;
}

and now in the external program the bytes can be read with peek(p).
 

WBahn

Joined Mar 31, 2012
32,887
Once again, people that refuse to look up what a function does deserve what they get when they opt, instead, to make unwarranted assumptions.

Why aren't you complaining about functions like:

int putc(int char, FILE *stream)

Gee, it takes an int. So that's misleading because it makes us think we can put an integer out to a file. And what's this 'int' that it is returning?

Or what about

int getc(FILE *stream)

Gee, this function is supposed to get a char from a file, but it returns an int. That's misleading and makes people thing that it reads an integer from the file and not a character.

And whether the function time takes significantly more time than the call overhead is beside the point.

You have a process that involves doing A and then doing B. Doing A takes 10% of the time and doing B takes 90% of the time. If you had a simple way to cut the amount of time associated with doing A in half, would you go, "Well, B takes most of the time and so I'm just going to ignore improving A"? Of course not. It's not a matter of having to pick one and only one way to improve performance. You improve performance every place you can. Sure, if it comes down to spending a lot of time and effort figuring out how to improve something, you will likely focus on B first. But if there is a simple, obvious way to improve A, why not do it? And, again, remember that these libraries date back to the days when processing power was so limited that people were often hand tweaking code to remove single clock cycles of computation time from a loop. That is why the C language standard is so permissive and is littered with so many things that are implementation defined or are outright undefined.
 

MrSoftware

Joined Oct 29, 2013
2,273
I might butcher this, so someone please correct me if I get this wrong..but here goes; memset() is very old, way back to before function prototypes were around. Back then you were allowed to call a function with any list of arguments, and they were assumed to be of type int; you could only specify the return type. So as long as what you were passing was the size of an int, all was happy. Since then the language has evolved, but changing all those well-known reliable and proven pre-existing functions would (1) probably break a lot of stuff that relies on its current behavior, and (2) serves no real purpose anyway.
 

WBahn

Joined Mar 31, 2012
32,887
I think you are spot on the money -- I had forgotten the old syntax in which the parameter types were defined within the function body since I've never even seen code old enough to use that, just a few historical examples here and there.
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
Hi,

Between yours and MrSoftware's replies i think i see what happened now.

Long ago an "int' would have been a byte i think, because that would have been the default type. That's still the default type on some platforms. So i have to agree that the reason for int was because of the progression of the hardware vs software, and no one bothered to change that. That explains the other function being like that too then.

I guess there's nothing we can do about this then except explain it every time someone comes up with a problem where they are trying to set a bunch of int's (as in an array) and find that they cant do it because the values they read back in the array are not what they expected to see. It would be good to be able to provide a good explanation for this which i think we can now.

Perhaps we've uncovered the fault with 'int' itself being a type that is always taken to be some default type.

Maybe another question then would be in the memset case of int c, how do we assume that the actual char being placed into memory is the lower order byte of c and not the upper order byte, or even some intermediate byte with in the four byte stretch of an int on a 32 bit machine.
 

MrSoftware

Joined Oct 29, 2013
2,273
You're thinking too hard. C is hardware agnostic; even if an int happened to be 1 byte on one platform, it likely was not on another. The language was young, there weren't a lot of features, and without prototypes function arguments had to be type int. It's like cars before electric start. There's no magical reason they weren't electric start, except that electric start had not been developed yet.

You can set an array of int's just fine with memset; if sizeof(int) happens to be > 1 on your platform, then just call memset() once for every byte you want to set, with the value for that byte. The least significant byte of "c" is used because that's what the documentation says it will use. ;)
 

WBahn

Joined Mar 31, 2012
32,887
Hi,

Between yours and MrSoftware's replies i think i see what happened now.

Long ago an "int' would have been a byte i think, because that would have been the default type. That's still the default type on some platforms. So i have to agree that the reason for int was because of the progression of the hardware vs software, and no one bothered to change that. That explains the other function being like that too then.
No. In the earliest standard an int was required to be of such size as to hold positive and negative values that correspond to a 16-bit 2's complement value. I can't absolutely guarantee that this was the case in the very first inception, but it was originally targeted for a 16-bit architecture (the PDP-11, if I'm correct), so not a surprise that the int was 16 bits.

I guess there's nothing we can do about this then except explain it every time someone comes up with a problem where they are trying to set a bunch of int's (as in an array) and find that they cant do it because the values they read back in the array are not what they expected to see. It would be good to be able to provide a good explanation for this which i think we can now.
Or, perhaps, we could encourage people to actually look up what a function does before they use it. Just a thought.

Perhaps we've uncovered the fault with 'int' itself being a type that is always taken to be some default type.
An int was NEVER meant to be a specific width. This is true of most of the primitive types in C. There are requirements that it must meet, but it can exceed them and still be perfectly compliant. That's why some machines have 16-bit ints, some have 32-bit ints, and some have 64-bit ints. You have macro constants provided by the compiler to determine what they are on a particular implementation. Even a char is not required to be one byte. Similarly, the source code alphabet is not required to be the same as the execution code alphabet and neither is required to be ASCII. It is only relatively recently that you have standard data types that ARE fixed-width, though most implementations offered them as implementation-specific extensions long ago.

Maybe another question then would be in the memset case of int c, how do we assume that the actual char being placed into memory is the lower order byte of c and not the upper order byte, or even some intermediate byte with in the four byte stretch of an int on a 32 bit machine.
We don't assume. THAT'S the point we have (very unsuccessfully) been trying to get across. We rely on the language standard, which requires that a cast from on integer data type to a smaller integer data type preserves those values that can be represented by both types AND that the representation be the same.
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
Hi again,

You mean you are saying that the very first use of 'int' was in a 16 bit machine not an 8 bit machine?
Currently there are uC's that use an int_8 for int as default, and others that use int_16. This probably came about later though.

If you look up the definition of memset some references are not that clear:
"The memset function fills the first length characters of the object pointed to by dst with the value c"

although they do mention characters, that is in contrast to "int c" which causes confusion.

Another reference is more clear, stating that it uses the unsigned char version of c but passes an int.

The question of program code optimization is not about removing 1 millionth of a percent of the time required to run the program code. That's because the perceived time is usually relative. Of course 10 percent may be taking this factor too far, but 1 percent would not be and certainly not 0.01 percent.
With some small percentage it just doesnt matter very much, in most cases, because the program code doesnt take that much time anyway.
For examples
A program that runs in 3600us and gets 1us added to it results in code that runs in 3601us.
A program that runs in 36000us and gets 10us added results in a total run time of 36010us.
Similarly, 360ms with 0.1ms added results in 360.1ms
3.600 seconds with 1ms added results in 3.601 seconds.
36.00 seconds with 0.01 seconds added results in 36.01 seconds.
360.0 seconds (6 minutes) with 0.1 seconds added results in 6 minutes and 1 second.
3600 seconds (one hour) with 1 second added results in 1 hour and 1 second run time.
36000 seconds (10 hours) with 10 seconds you get the idea.
Now we come to something may seem significant, but really isnt:
360000 seconds (100 hours) with 100 seconds (1 minute, 40 seconds) results in 100 hours plus that extra 1 minute 40 seconds which we may round up to 2 minutes. So we actually have to wait 2 more minutes to get the result. That seems like a lot of waiting around, but if we can wait 100 hours then we will find almost no problem with waiting another 2 minutes.
That's why time profiling ends up being relative, not absolute.
That's also why we have inline functions because sometimes those function call overheads do matter. If that were not so, we would not need inline functions ever.

So if you want to believe that trimming 0.01 percent off of a function is going to buy you something great, that's up to you :)

If you look at a lot of functions that you've written they all take significant time so the call overhead is insignificant. It's the same with resistor values, where 1001 ohms works in most cases where 1000 ohms works. If you find one where the call is not insignificant, then the code could be written so that the one call does more and thus then again makes the call overhead insignificant. When this doesnt apply then a solution might be to declare it as inline.
Also, i take it you never used a function that takes type char as argument then? :)
 

WBahn

Joined Mar 31, 2012
32,887
Hi again,

You mean you are saying that the very first use of 'int' was in a 16 bit machine not an 8 bit machine?
Currently there are uC's that use an int_8 for int as default, and others that use int_16. This probably came about later though.

If you look up the definition of memset some references are not that clear:
"The memset function fills the first length characters of the object pointed to by dst with the value c"
Okay. Let's look up the definition of memset():

7.21.6.1 The memset function
Synopsis
1 #include <string.h>
void *memset(void *s, int c, size_t n);
Description
2 The memset function copies the value of c (converted to an unsigned char) into
each of the first n characters of the object pointed to by s.
Returns
3 The memset function returns the value of s.

That's straight from the C Language Standard -- admittedly the WG14/N869 Draft Standard, which is what I have handy.

No one can control the hundred or thousands of "references" you can find on the internet or how sloppy many of them might be.

So if you want to believe that trimming 0.01 percent off of a function is going to buy you something great, that's up to you :)
No one has claimed that this increases performance by some astronomical amount. Whether it does or not is irrelevant! What YOU have to show is why the writer of a function should NOT implement a trivially simple performance enhancement in a function that is in a standard library that is going to be used in millions of programs for decades to come, where all of the users of the function will get whatever performance enhancement results, whether significant or negligible, for free without having to lift a finger, and that in no way prevents anyone from optimizing any other part of the code.

I suppose the compiler implementers are wrong when they align objects within a structure definition so that they sit on word boundaries even if this means adding padding bytes to the structure.
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
Hello again,

Not sure what you mean when you say that you suppose the writers were wrong when implementing a structure with word aligned elements with padding bytes added.

Unfortunately we have been so far operating on assumptions which have not been proved yet so it's hard to argue right down to the machine cycle here.
First, we have to see the implementation of memset so we dont even know for sure if using int c leads to a benefit. If we pass a four byte value to a function that only uses the least significant byte, it has to come up with a way to get that byte, which would have to use another instruction anyway which would be similar to a cast like c1=unsigned char) c; but probably comes down to a logical statement like c1=c&0x000000FF. What else we would need to know is how a char is passed to a function as int when it starts out as a char anyway. I dont think you can push bytes so maybe it has to pad the char first before pushing. That would take one extra instruction. It would make sense that someone would think this would be faster because of that one extra instruction, even though it adds an incredibly small time to the function call, but if it has to do that anyway then there is no difference. So i guess in that case it would be a choice between readability and eeking out every machine cycle possible. I believe the asm implementation of the function that pokes the values into memory is a single instruction, although it can take significant time to execute due to the number of bytes that have to be set.
Another thing we might have to prove is if there is an even better way of doing it, but seeing the actual implementation would help with any of these questions.

The proof for why NOT to do it would come from the implementation, it cant come from analyzing the C language definition. That's because we dont know how certain things are being done in the machine code. If using type int was faster than using char by 0.01 percent then maybe there is some case to be made, but only if that percentages rises with low byte counts. But without knowing for sure, using type int may actually take longer than type char.

So do you think you will never write code that passes type char anymore or what, or will you evaluate the significance first using a time profile test?

I agree you are making some sense here, but the proof must come in the implementation.
Also, it would be interesting i think to do a test where we pass char in one function and type int in another function, both functions doing the same thing. I have a feeling this will be hard to test though because of the pipeline structure of modern computers, and again we run into the "small time added to more significant time" reasoning, This would take us back to the original idea that in the early computers it made a difference but in modern computers it doesnt.

I have found some surprising results using different CPU's. Using the AMD Fx series multicore processor i discovered to my surprise that they do not actually contain eight full cores. They contain 8 integer cores and only 4 floating point cores. This caused a slow down of some of my software that depended on using all 8 cores at the same time. I had to rewrite some of my programs to work in gigantic integers to get the speed back. In that case it would not matter if i called memset with 10 chars being passed because it still would have paled in comparison to the time the rest of the routines were taking to process the data streams. This was used in image processing routines that needed to be as fast as possible due to the immense amount of data that had to be processed.
 

ErnieM

Joined Apr 24, 2011
8,415
Not sure what you mean when you say that you suppose the writers were wrong when implementing a structure with word aligned elements with padding bytes added.
That statement employs a technique knows as "irony" to make a strong point by pretending to state the reverse. Of course structures are paded for efficiency.

The rest... you seem to be continiously suprised by information freely available if you took the time to Read The Fine Manual RTFM.
 

WBahn

Joined Mar 31, 2012
32,887
Hi again,

You mean you are saying that the very first use of 'int' was in a 16 bit machine not an 8 bit machine?
Currently there are uC's that use an int_8 for int as default, and others that use int_16. This probably came about later though.
I see that I forgot to address this.

The machines that were the original targets of Unix and C were the PDP-7 (18-bit) and the PDP-11 (16-bit). At the time is was generally held that no viable operating system could be written in anything other than assembly language, which underscores how critical it was to design a high-level language that was as sensitive to performance optimization as possible. Clock speeds were in the 3 MHz to 15 MHz range and programs had a 64 kB address space to work with.

What current microcontrollers use is completely beside the point; I don't think anyone gave really serious thought to using anything other than assembly language to program a microcontroller for a very long time after the language was established -- microcontrollers were just way too resource starved for anything else. On top of that, most MCUs that use C (or some other high level language) do not use the same version as you would use on a microprocessor and, instead, have dialects that have both restrictions and enhancements to accommodate the unique limitations and capabilities of that family of MCU.
 

nsaspook

Joined Aug 27, 2009
16,330
The proof for why NOT to do it would come from the implementation, it cant come from analyzing the C language definition. That's because we dont know how certain things are being done in the machine code. If using type int was faster than using char by 0.01 percent then maybe there is some case to be made, but only if that percentages rises with low byte counts. But without knowing for sure, using type int may actually take longer than type char.

So do you think you will never write code that passes type char anymore or what, or will you evaluate the significance first using a time profile test?
https://gcc.godbolt.org/#{"version"...AA","compiler":"armhfg482","options":"-O3"}]}

You are very x86/x64 centric in your reasoning of how and why the C language operates. Unaligned memory access solutions (like __packed telling the compiler you know better) that have worked for decades on x86 cpus crash when used on other cpu architectures.
 

MrSoftware

Joined Oct 29, 2013
2,273
Memory alignment for performance is a real thing and makes a very real performance difference on some platforms. But that doesn't have anything to do with the argument types for the memset() function.

C the language itself doesn't know or care about the platform you're compiling for, but C does give you the ability to tailor your code to a specific platform if you choose. The compiler may or may not have platform specific features that you can choose to use. It's up to you as the programmer to write your code appropriately. I spent many years writing C libraries that ran on multiple platforms (x86, x64, SPARC, PPC, MIPS) and even more operating systems. The same exact code compiled on all those different platforms. There was a platform-specific layer to handle platform specific differences for the low level I/O, but 95% of the code was compiled on all platforms. The libraries operated on data at the byte level, so memory alignment, endiannes and compiler differences were real issues we had to tackle. Variable sizes, byte order and byte packing were never assumed, everything had to be explicit. I guess my point is, when you look at a standard library function definition, such as for memset(), understand that it will (or should) work the same on every platform. The argument types were not chosen because of the hardware that the author happened to be using at the time.
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
Memory alignment for performance is a real thing and makes a very real performance difference on some platforms. But that doesn't have anything to do with the argument types for the memset() function.

C the language itself doesn't know or care about the platform you're compiling for, but C does give you the ability to tailor your code to a specific platform if you choose. The compiler may or may not have platform specific features that you can choose to use. It's up to you as the programmer to write your code appropriately. I spent many years writing C libraries that ran on multiple platforms (x86, x64, SPARC, PPC, MIPS) and even more operating systems. The same exact code compiled on all those different platforms. There was a platform-specific layer to handle platform specific differences for the low level I/O, but 95% of the code was compiled on all platforms. The libraries operated on data at the byte level, so memory alignment, endiannes and compiler differences were real issues we had to tackle. Variable sizes, byte order and byte packing were never assumed, everything had to be explicit. I guess my point is, when you look at a standard library function definition, such as for memset(), understand that it will (or should) work the same on every platform. The argument types were not chosen because of the hardware that the author happened to be using at the time.
Hi,

I agree that byte aligning does not apply directly especially since some things have to be word aligned (2 byte boundaries) and some things have to be dword aligned (4 byte boundaries). That's why i was questioning that reply. It has a little to do with it though i guess because of the way the CPU must handle memory.

The point WBahn was making i think was that the original implementation was on a 16 bit machine and it probably handled 16 bit memory objects so it had no native char object. That's the same way it stands today where we have 32 or 64 bit memory objects (from the standpoint of the CPU) and so it may make a performance difference. I would have to see proof of this though in the implementation to be sure, and that is because questions come up as to how the compiler creates the code for a function call with argument char as compared to argument int. If there is no native char, then it must be working with int's anyway. I dont think there is a way to push a single char onto the stack in 32 bit architecture. That would mean that if we pass a char, it would really be an int that gets pushed anyway, but how the implementation does this is the main question now. Does it have to use an instruction that converts a char to int or is the char stored as an int all along. If it's a single byte in memory the CPU has to read at least 32 bits (maybe 64 or 128) and then somehow get that one char to make the difference.
It's been a while since i looked at the basic 386 instruction set using the 32 bit mode. In fact it's been many years now so id have to look through a lot of material to find out for sure.

My gut on this is that it doesnt make any difference in a modern system, but it may have a long time ago when CPU's were more straightforward. Today we have pipelines and cache hits and misses, so the exact timing probably depends on an entire block of code as to how the pipeline handles it rather than one instruction or two.

So in the end i think i would agree that the first machines that used C were 16 bit and the C writers were probably following the same philosophy that Wbahn follows, that any little savings in instruction times should be incorporated into a base language function no matter how small, and then it would probably make more of a difference than today even though it's still a small difference.

I guess i see this as a 'noise floor' problem, where when we have a tiny difference that fluctuates and we see a smaller systematic error, the systematic error is absorbed into the noise floor where it goes unnoticed for all practical purposes. Also, because i have written so many programs both large and small in various languages over the years, i would even go so far as to say that probably even back then it was the same situation. The question of why NOT to do it that way may be a bit pedantic, but i think is still an interesting question. The answer would have to come as a direct comparison where we can find actual time numbers that convey the savings in time, if any, and then relate that to the practical aspect which comes from the body of the code, and compare that to what would be the perfect prototype which i believe would be to use "char c" rather than "int c". So the only gain then is to be able to write a perfect prototype which conveys exactly what is expected of the arguments, a practice that is very economical from a program maintenance standpoint. This view again comes from experience of writing literally thousands of programs and include files and their maintenance over the years.

Perhaps a list of pro's and con's is in order.
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
https://gcc.godbolt.org/#{"version":3,"filterAsm":{"labels":true,"directives":true,"commentOnly":true},"compilers":[{"sourcez":"MQSwdgxgNgrgJgUwAQB4DOAXOUQCMB0AFgHwBQoksiqAtgjQPYBOAnkWRiwA4KIBmSGGDQgA5mF5JwGQQGYATAG5SMBUiYIAhnAUAKCA2EyAbgxBwAVEi4YmASiSkA3o6Ru589QjQwoGRa7udDQQXCy6AGQaPn4ANNa28QAsdgGk7l4YMExgXjH+jgC+QAAA","compiler":"armhfg482","options":"-O3"}]}

You are very x86/x64 centric in your reasoning of how and why the C language operates. Unaligned memory access solutions (like __packed telling the compiler you know better) that have worked for decades on x86 cpus crash when used on other cpu architectures.
Hi,

Yes, and that is because i am working with those architectures and that comes up quite a bit in forums.
Thus, except for brief comparison maybe i dont consider any other architectures.
If you would like to add some of that here that's your choice. It probably wont matter to me though because i work on x86 and up, although i will work on some 8 bit uC machines.
 

WBahn

Joined Mar 31, 2012
32,887
As far as I know, the PDP-11 had byte-oriented instructions. But that's neither here nor there. The C language standard specifically encourages compiler implementers to make the data type int match the natural data type of the hardware (provided it is at least 16 bits). Why would they have done this if not to at least give the compiler implementers the opportunity to take full advantage of any performance gains that could be had by doing so? Now imagine if they had then gone and made all of the standard library functions use the smallest data sizes possible for each purpose. They would have instantly stolen from them much of the potential advantages they had just gone out of your way to encourage them to exploit!
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
That statement employs a technique knows as "irony" to make a strong point by pretending to state the reverse. Of course structures are paded for efficiency.

The rest... you seem to be continiously suprised by information freely available if you took the time to Read The Fine Manual RTFM.
Hello there,

Thank you for participating in this thread. I appreciate input from all possible angles whether i agree at first or not.

However, in this case you are commenting on something i never asked about. It's not about reading the manual that tells you everything you ever wanted to know about anything in the universe and it being 100 percent accurate so you walk away with the mind of God and can therefore do anything and everything possible, it's about glancing at the prototype and being able to understand what arguments the function takes and have a basic understanding of how it will handle them. it is much better practice to pass the type of variable your function needs for that argument. For an example:
memset(void*,int_128 c, int_128 n);

Note in this example we not only cant use the entire range of c, we cant use the entire range of n either.
In a base library language it is slightly different, but i have a feeling it never makes a difference for this particular function.
 

Thread Starter

MrAl

Joined Jun 17, 2014
13,711
As far as I know, the PDP-11 had byte-oriented instructions. But that's neither here nor there. The C language standard specifically encourages compiler implementers to make the data type int match the natural data type of the hardware (provided it is at least 16 bits). Why would they have done this if not to at least give the compiler implementers the opportunity to take full advantage of any performance gains that could be had by doing so? Now imagine if they had then gone and made all of the standard library functions use the smallest data sizes possible for each purpose. They would have instantly stolen from them much of the potential advantages they had just gone out of your way to encourage them to exploit!
Hi,

So you are saying the original designers of the C language were Gods and never made a mistake? :)

But again we would have to look at some implementations to be sure. I dont think you can push a char anyway, so it would have to be converted to whatever the base data width is for that platform.
Back in the 16 bit machines i think i remember pushing bytes, but i think that changed with the 32 bit architectures. If we push a register, it has to be 32 bits. If we pop a reg it has to be 32 bits. When we read a constant value it has to be converted by the compiler and turned into a constant in a memory location which gets stored on disk which later gets loaded by the exe loader when the program is run. At that time the CPU will read that value directly (older system) or place it into a cache where it will be read later. An interesting question then is how each compiler (older and newer) converts that value during compile time. Does it convert it into a byte if possible and store it in the disk file, or does it convert it into a four byte value and store that in the disk file. If it is the latter case, then it will handle type int the same as type char when we get a constant value. I would think a compiler with target 32 bit platform would use 32 bits.

When we get a non constant value (less typical but still possible) the compiler has to convert that to a pointer to memory and store that as well as the 'load' instruction as well as the push and pop later. The question now is when it creates the pointer, what instruction does it use to read that value using that pointer. It must push a 32 bit value, so it would make sense to use a pointer to a 32 bit value (which would imply a compiler optimized for 32 bits), but there must be byte loads too so it's unclear to me at the moment if it would load a 8 bit value and then convert that to 32 bit dynamically. That would be just one instruction however if it did have to do that.

We had a lot of change since the early computers which is not reflected in the C language, and for this instruction nobody probably thinks it is really necessary. They would rather write the function prototype one way and the instructions for that function differently.

Here's a funny extreme example of a new memset function:
void* memset(void*, int a[1024],int n);

Here we use a 1024 byte array to pass a single byte at the low order byte of a[0] (ha ha) :)
 
Last edited:
Top