Few people know out-of-the-box Windows doesn’t allow you to create files whose full name is longer than 260 characters.
So, you can’t create C:\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234.txt until you turn on a feature called “long paths”.
It’s not the file name but the whole path, so you can create the folder C:\1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456 but you can’t create files in it because adding the “\” separator will be the 260th character.
The reason is simple. Back in C when you needed somewhere to store a file, you created a little buffer of N characters to store it. Every operating system had its own limit, but to access that you had to include extra headers and then remember/type the name of the variable, and usually the header it was in wasn’t short or simple and if you couldn’t remember the name, finding it was a pain. (PATHMAX? MAXPATH? PATH_MAX? PATHLENMAX?)
And so, in the spirit of y2k and 64KB, the just typed one of 250, 255, 256, 260 or similar. Why would you need a longer filename? Well, knowing other programmers were going to likely use upto 256 in code using the operating system, Microsoft allowed 4 bytes of padding.
Anyway, I’ve found this to be a source of amusement observing how programmers deal with discovering this. Typically outrage at “stupid windows”.
Why?
Because times have changed. Today, instead of using the system-provided constant, they type 500, 510, 511, or 512 if they’re over 30; 1000, 1023, 1024 if they’re over 20 and 4096 if they’re under 24.
And my absolute favorite moment was this last week, reviewing code from a former colleague, friend and well-respected engineer who was all busy a-grumbling about having to work around this limitation and right there in the first line of his work-around code he had written:
// paraphrased
bool readFile(Type assetType, const string& name)
{
char filename[250];
const string& assetFolder = getAssetFolderFromType(assetType);
sprintf(filename, "%s/%s", assetFolder.c_str(), name.c_str());
std::ifstream instream(filename);
...
So I did the little me-thing of asking what this code was solving for (“the 260 character limit”) and he gave me an example filepath for testing it, and then I asked “and that will fit in your filename buffer?”, “yes, the name is just FlowerA.png <additional grumbling about the 260 limit>“, “but you’re combining the name with the path”.
Edit 1
bool readFile(Type assetType, const string& name)
{
char filepath[250];
“Great, and our FlowerA.png filepath will fit into it?”
“obviously as long as its under 250 characters“
“which is less than 260”
“what? fffuuufufufuuufuufuuu, damn Microsoft“
“How is this their fault? They even gave you 10 extra characters if you want to do it the hard way, or 32767 characters if you opt-in to long paths. But the 260 limit is partly there for to try and protect against your bug.”
“what bug?“
“Well you used 250 and sprintf throughout your code, and we already know that some of the paths alone are over 260 characters plus long filenames coming out to 300”
“it’s not my bug if the operating system can’t handle a path that long and the paths get truncated“
“That’s your second bug, that you hardcoded too small a number, but the first bug is that sprintf doesn’t truncate”
“of course it does, where else can it put the rest of the string. oh. I hate windows, this has never been a problem on Mac”
“Actually, you totally have, there’s a whole bunch of can’t-repro bugs that went away when people used a different setup that you wrote off as being having done it wrong the first time. The difference is everyone else has their full user name and you have a three letter username, which brings you in at 249 characters for the example case”
My alternative for his code:
bool readFile(Type assetType, const string& assetPart)
{
filesystem::path assetPath(getAssetFolderFromType(assetType));
fullpath /= assetPart; // join with native directory separator.
std::ifstream file(fullpath);
if (!file.good())
throw huston::fileaccess_exception("could not open file", getErrno()); // getErrno = his function
...
Him: “But how does that solve the 260 character limit?“
It doesn’t, but it also doesn’t either truncate the names to 249 characters or stomp the stack. The code opts in to long paths in its manifest, and the user needs to ensure they have long paths enabled. You can do a registry check or have your own path checking method to warn users if they do encounter a long path without having enabled the facility in the OS.
It tickled me for two reasons. First, one of my current coworkers did the same 250 thing (but fortunately he knows to use snprintf if not filesystem/string as an arg to ifstream{}).
Secondly, he had gone thru all his code and done pretty much the same thing everywhere with the 250. This was a distraction for him that needed fixing superfast and he couldn’t task anyone else to it, and in his head what he’d been doing was ensuring the paths were below Microsoft’s limit, for the half a second he’d thought about it.
The performance issue was, of course, that he was using streams and finding them slow, but if he didn’t then his programs would run out of file handles when he was using FILE objects.
This guy is not an idiot. He’s written some truly amazing libraries that are very important to modern AI and graphics coding. This is just not a part of C/C++ he has spent much time in. He literally hadn’t used the layer-3 C io in C++ in a decade.
Quite the reminder that you should never think you know more than 60% of C++ :)
Recent Comments